The Blender files are put in a S3 Bucket, a script controls the pipeline operation and EC2 machines are scaled inside a VPC with two subnets, with one set doing the rendering and the other set stitching the renders together to create the final video file. This output is then sent to another S3 bucket for use by the dashboards which are used by the end users.
The following Cloudformation file is inspired by the AWS EC2 Workshops and brings up the Cloud components required to run the AWS pipeline. I explained to the DevOps specialists for the client how Blender works and what it needs to have to render each image and how it generates its output. They replicated the architecture with the structure that would work for the client’s project. After a few test runs, we got it working!
Resources:
BlenderVpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
InstanceTenancy: default
Tags:
- Key: Id
Value: BlenderStack/Vpc
Metadata:
aws:cdk:path: BlenderStack/Vpc/Resource
BlenderSubnet:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.0.0/24
VpcId:
Ref: BlenderVpc
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
MapPublicIpOnLaunch: true
Tags:
- Key: aws-cdk:subnet-name
Value: BlenderSubnet
- Key: aws-cdk:subnet-type
Value: Public
- Key: Id
Value: BlenderStack/Vpc/BlenderSubnet1
Metadata:
aws:cdk:path: BlenderStack/Vpc/BlenderSubnet1/Subnet
BlenderRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: BlenderVpc
Tags:
- Key: Id
Value: BlenderStack/Vpc/BlenderSubnet1
Metadata:
aws:cdk:path: BlenderStack/Vpc/BlenderSubnet1/RouteTable
BlenderRtAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: BlenderRouteTable
SubnetId:
Ref: BlenderSubnet
Metadata:
aws:cdk:path: BlenderStack/Vpc/BlenderSubnet1/RouteTableAssociation
BlenderDefaultRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId:
Ref: BlenderRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId:
Ref: BlenderGateway
DependsOn:
- BlenderGatewayAttachment
Metadata:
aws:cdk:path: BlenderStack/Vpc/BlenderSubnet1/DefaultRoute
BlenderSubnet2:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.1.0/24
VpcId:
Ref: BlenderVpc
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ""
MapPublicIpOnLaunch: true
Tags:
- Key: aws-cdk:subnet-name
Value: BlenderSubnet
- Key: aws-cdk:subnet-type
Value: Public
- Key: Id
Value: BlenderStack/Vpc/BlenderSubnet2
Metadata:
aws:cdk:path: BlenderStack/Vpc/BlenderSubnet2/Subnet
BlenderSubnet2RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: BlenderVpc
Tags:
- Key: Id
Value: BlenderStack/Vpc/BlenderSubnet2
Metadata:
aws:cdk:path: BlenderStack/Vpc/BlenderSubnet2/RouteTable
BlenderSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: BlenderSubnet2RouteTable
SubnetId:
Ref: BlenderSubnet2
Metadata:
aws:cdk:path: BlenderStack/Vpc/BlenderSubnet2/RouteTableAssociation
BlenderGateway2Attachment:
Type: AWS::EC2::Route
Properties:
RouteTableId:
Ref: BlenderSubnet2RouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId:
Ref: BlenderGateway
DependsOn:
- BlenderGatewayAttachment
Metadata:
aws:cdk:path: BlenderStack/Vpc/BlenderSubnet2/DefaultRoute
BlenderGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Id
Value: BlenderStack/Vpc
Metadata:
aws:cdk:path: BlenderStack/Vpc/IGW
BlenderGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId:
Ref: BlenderVpc
InternetGatewayId:
Ref: BlenderGateway
Metadata:
aws:cdk:path: BlenderStack/Vpc/VPCGW
BlenderSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: BlenderStack/securityGroup
GroupName: BlenderSubnet
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
Description: Allow all outbound traffic by default
IpProtocol: "-1"
VpcId:
Ref: BlenderVpc
Metadata:
aws:cdk:path: BlenderStack/securityGroup/Resource
BlenderLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateData:
SecurityGroupIds:
- Fn::GetAtt:
- BlenderSecurityGroup
- GroupId
TagSpecifications:
- ResourceType: instance
Tags:
- Key: Id
Value: BlenderStack/launchTemplate
- ResourceType: volume
Tags:
- Key: Id
Value: BlenderStack/launchTemplate
UserData:
Fn::Base64: |-
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="==MYBOUNDARY=="
--==MYBOUNDARY==
Content-Type: text/x-shellscript; charset="us-ascii"
#!/bin/bash
echo "ECS_CLUSTER=BlenderSpotEC2" >> /etc/ecs/ecs.config
echo "ECS_ENABLE_SPOT_INSTANCE_DRAINING=true" >> /etc/ecs/ecs.config
echo "ECS_CONTAINER_STOP_TIMEOUT=90s" >> /etc/ecs/ecs.config
echo "ECS_ENABLE_CONTAINER_METADATA=true" >> /etc/ecs/ecs.config
--==MYBOUNDARY==--
LaunchTemplateName: BlenderSubnet
Metadata:
aws:cdk:path: BlenderStack/launchTemplate/Resource
BlenderBucket:
Type: AWS::S3::Bucket
UpdateReplacePolicy: Delete
DeletionPolicy: Delete
Metadata:
aws:cdk:path: BlenderStack/bucket/Resource
BlenderRepository:
Type: AWS::ECR::Repository
Properties:
RepositoryName: rendering-with-batch
UpdateReplacePolicy: Delete
DeletionPolicy: Delete
Metadata:
aws:cdk:path: BlenderStack/repository/Resource
BlenderCloud9:
Type: AWS::Cloud9::EnvironmentEC2
Properties:
InstanceType: t2.micro
Name: BlenderSubnet
SubnetId:
Ref: BlenderSubnet
Tags:
- Key: SSMBootstrap
Value: BlenderSubnet
Metadata:
aws:cdk:path: BlenderStack/cloud9env/ec2env/Resource
BlenderBootstrap:
Type: AWS::SSM::Document
Properties:
Content:
schemaVersion: '2.2'
description: Bootstrap Cloud9 Instance
mainSteps:
- action: aws:runShellScript
name: C9bootstrap
inputs:
runCommand:
- "#!/bin/bash"
- echo '=== Installing packages ==='
- sudo yum -y install jq
- sudo pip install boto3
- echo '=== Resizing file system ==='
- sudo growpart /dev/xvda 1
- sudo resize2fs /dev/xvda1
DocumentType: Command
Name: BootstrapDocument
Metadata:
aws:cdk:path: BlenderStack/cloud9env/SSMDocument
BlenderCloud9Association:
Type: AWS::SSM::Association
Properties:
Name: BootstrapDocument
Targets:
- Key: tag:SSMBootstrap
Values:
- BlenderSubnet
DependsOn:
- BlenderCloud9CustomResource
- BlenderBootstrap
Metadata:
aws:cdk:path: BlenderStack/cloud9env/SSMAssociation
BlenderCloud9Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service:
Fn::Join:
- ""
- - ec2.
- Ref: AWS::URLSuffix
Version: "2012-10-17"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
RoleName: SSMInstanceProfile
Metadata:
aws:cdk:path: BlenderStack/cloud9env/FISRole/Resource
BlenderCloud9Profile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- Ref: BlenderCloud9Role
Metadata:
aws:cdk:path: BlenderStack/cloud9env/fisinstanceprofile
BlenderCloud9ServiceRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service: lambda.amazonaws.com
Version: "2012-10-17"
ManagedPolicyArns:
- Fn::Join:
- ""
- - "arn:"
- Ref: AWS::Partition
- :iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Metadata:
aws:cdk:path: BlenderStack/cloud9env/bootstrapLambda/ServiceRole/Resource
BlenderCloud9Policy:
Type: AWS::IAM::Policy
Properties:
PolicyDocument:
Statement:
- Action:
- ec2:DescribeInstances
- ec2:ModifyVolume
- ec2:AssociateIamInstanceProfile
- ec2:ReplaceIamInstanceProfileAssociation
- ec2:RebootInstances
- iam:ListInstanceProfiles
- iam:PassRole
- ssm:SendCommand
Effect: Allow
Resource: "*"
Version: "2012-10-17"
PolicyName: BlenderCloud9Policy
Roles:
- Ref: BlenderCloud9ServiceRole
Metadata:
aws:cdk:path: BlenderStack/cloud9env/bootstrapLambda/ServiceRole/DefaultPolicy/Resource
BlenderCloud9Lambda:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: |
import time
import boto3
import cfnresponse
def retrieve_cloud9_instance(env_id):
print("Retrieving environment's instance...")
client = boto3.client('ec2')
return client.describe_instances(
Filters=[
{
'Name': 'tag:aws:cloud9:environment',
'Values': [
env_id,
]
},
]
)['Reservations'][0]['Instances'][0]
def resize_volume(volume_id, new_size):
print('Resizing EBS volume...')
client = boto3.client('ec2')
client.modify_volume(
VolumeId=volume_id,
Size=new_size
)
print('EBS volume resized')
def associate_ssm_instance_profile(c9_env_id, profile_arn):
instance_data = retrieve_cloud9_instance(c9_env_id)
client = boto3.client('ec2')
while instance_data['State']['Name'] != 'running':
print('Waiting for the instance to be running to attach the instance profile...')
time.sleep(5)
instance_data = retrieve_cloud9_instance(c9_env_id)
print('Attaching instance profile...')
client.associate_iam_instance_profile(
IamInstanceProfile={'Arn': profile_arn},
InstanceId=instance_data['InstanceId']
)
print('Instance profile associated. Restarting SSM agent...')
client.reboot_instances(
InstanceIds=[
instance_data['InstanceId']
]
)
print('Instance rebooted')
def handler(event, context):
if event['RequestType'] == 'Create':
# Extract context variables
c9_env_id = event['ResourceProperties']['cloud9EnvId']
ebs_size = int(event['ResourceProperties']['ebsSize'])
profile_arn = event['ResourceProperties']['profile_arn']
try:
# Retrieve EC2 instance's identifier and its EBS volume's identifier
instance_data = retrieve_cloud9_instance(c9_env_id)
volume_id = instance_data['BlockDeviceMappings'][0]['Ebs']['VolumeId']
# Resize the EBS volume
resize_volume(volume_id, ebs_size)
# Associate the SSM instance profile
associate_ssm_instance_profile(c9_env_id, profile_arn)
except Exception as e:
cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': e.args[0]})
return
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
Role:
Fn::GetAtt:
- BlenderCloud9ServiceRole
- Arn
Handler: index.handler
Runtime: python3.7
Timeout: 300
DependsOn:
- BlenderCloud9Policy
- BlenderCloud9ServiceRole
Metadata:
aws:cdk:path: BlenderStack/cloud9env/bootstrapLambda/Resource
BlenderCloud9CustomResource:
Type: AWS::CloudFormation::CustomResource
Properties:
ServiceToken:
Fn::GetAtt:
- BlenderCloud9Lambda
- Arn
cloud9EnvId:
Ref: BlenderCloud9
ebsSize: 40
profile_arn:
Fn::GetAtt:
- BlenderCloud9Profile
- Arn
UpdateReplacePolicy: Delete
DeletionPolicy: Delete
Metadata:
aws:cdk:path: BlenderStack/cloud9env/bootstrapLambdaCustomResource/Default
BlenderEcsRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service:
Fn::Join:
- ""
- - ec2.
- Ref: AWS::URLSuffix
Version: "2012-10-17"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonS3FullAccess
- arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role
Metadata:
aws:cdk:path: BlenderStack/ecsRole/Resource
ecsinstanceprofile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- Ref: BlenderEcsRole
Metadata:
aws:cdk:path: BlenderStack/ecsinstanceprofile
CDKMetadata:
Type: AWS::CDK::Metadata
Properties:
Analytics: v2:deflate64:H4sIAAAAAAAA/1VRwW7CMAz9lt1DGOXEbaxjCGnSqoK4B9eIjDauEmeoivLvSykdcPLzy9Oz/TKTs2whX1/e1MVNoDpPA5BFGbas4CxKdOQtoMiP5ttz61nkZBxbDwl5x9Q8Sh5x0lWaNZkoeuuAkMmwb6F/2xe5KPyh1rD1B4Pcc3dUkmfcqUONd/7OLZ0j0Kp3/hf3YGMYbVKuFeNFdbcxt27J6ZxTg4bFFsFbzd3akm+vA56IL+UNnHbYtLUajJ+ZKNxchncP52HZAUWBYGUosSWnmWw3pDF2UUBNvlrIsIJsZX61JXNdJqke2lWeJXvXyJD4DwI/ah5ujkKrJChpSGesm/QpygAWlo46UQWlcK9bDCiKWjWHSsnwma4ZwxtxjFEUHZ/ITOdyIWcvP07rifWGdYOyHOofxvWhZCoCAAA=
Metadata:
aws:cdk:path: BlenderStack/CDKMetadata/Default
Condition: CDKMetadataAvailable
Outputs:
Subnet1:
Value:
Ref: BlenderSubnet
Subnet2:
Value:
Ref: BlenderSubnet2
LaunchTemplateName:
Value: BlenderSubnet
BucketName:
Value:
Ref: BlenderBucket
BlendFileName:
Value: blendfile.blend
RepositoryName:
Value:
Ref: BlenderRepository
ECSInstanceProfile:
Value:
Fn::GetAtt:
- ecsinstanceprofile
- Arn
Conditions:
CDKMetadataAvailable:
Fn::Or:
- Fn::Or:
- Fn::Equals:
- Ref: AWS::Region
- us-east-1
- Fn::Equals:
- Ref: AWS::Region
- us-east-2
- Fn::Or:
- Fn::Equals:
- Ref: AWS::Region
- us-west-1
- Fn::Equals:
- Ref: AWS::Region
- us-west-2
