AWS Deadline Cloudのカスタマーマネージドフリートを構築してレンダリングする (Houdini)

AWS Deadline Cloudがリリースされてからしばらく経ちましたが、皆さま使われていますか？
弊社ではCMFを使用して構築・運用を行っています。AWS Dead CloudはSMFの場合比較的簡単に構築ができますが、CMFを使用すると一気にハードルが上がります。


本記事ではHoudiniをインストールしたLinuxワーカーのフリートを例にして紹介します。

## フリートとは
ジョブを実行するサーバーのことをワーカーと呼びます。ここは従来のDeadlineと同じです。


AWS Deadline Cloudでは、このワーカーの集まりのことをフリートと呼び、フリートに対して自動スケーリングの設定等を行います。

SMFではAWSが提供するセットアップ済みのワーカーを使用します。またイベントベース自動スケーリングの設定もフリート設定時に自動で構築されるため、非常にお手軽に構築が可能です。

一方で、SMFでは基本的にユーザーがワーカーをカスタマイズすることはできません。
また利用できるソフトウェアに制限があり、デフォルトでは特定のソフトウェアの特定のバージョンでのみ利用可能です。

要するに、ワーカーに関して詳細な制御を行いたい場合はCMFを使用せよということです。

またCMFの場合は、AWSクラウド外（オンプレミス、他クラウド）にワーカーをセットアップすることも可能ですが、本記事ではAWSクラウド上への構築に絞って話を進めます。


:::note info
カスタムのCondaPackageを作成して使用することでSMFをカスタマイズすることも可能みたいです。こちらも気になるところですが、この記事ではCMFにフォーカスします
https://aws.amazon.com/jp/blogs/media/create-a-conda-package-and-channel-for-aws-deadline-cloud/
:::

## CMF(customer-managed fleet)で必要になる追加の作業
CMFの場合はSMFと比べて以下の作業が必要になります。

- DeadlineCloudワーカーエージェントのインストール
- ジョブ用のソフトウェアのインストール
- ライセンスエンドポイントの設定
- AMIの作成
- オートスケーリンググループの構築

各作業については公式ドキュメントにも記載がありますが結構大変です。。
https://docs.aws.amazon.com/ja_jp/deadline-cloud/latest/userguide/manage-cmf.html


本記事ではCMFの構築を簡単にするため、
- EC2 Image Builderによってワーカー構築作業をコード管理する
- CMF 構築一式をCloudFormationによってコード管理する

の２つを紹介します。

## EC2 Image Builderによってワーカー構築作業をコード管理する
Image Builderを使用してDeadlineCloudワーカーエージェント、各ソフトウェアがインストールされたマシンイメージを構築します。

Image BuilderについてはこちらのClassmethodの記事がわかりやすいです。
https://dev.classmethod.jp/articles/ec2-image-builder-primer/

#### DeadlineCloudWorkerAgentのインストールコンポーネント
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492828/c23c64fd-6680-91a3-e87a-fbcda7b11b18.png)
<details><summary>コンポーネント定義</summary>

```yaml
name: InstallDeadlineCloudWorkerAgent
description: Install Deadline Cloud Worker Agent on Amazon Linux 2023
schemaVersion: 1.0

parameters:
  - FarmId:
      type: string
      default: ''
      description: The Deadline Cloud Farm ID
  - FleetId:
      type: string
      default: ''
      description: The Deadline Cloud Fleet ID

phases:
  - name: build
    steps:
      - name: CreateAndConfigurePythonVenv
        action: ExecuteBash
        inputs:
          commands:
            - python3 -m venv /opt/deadline/worker
            - source /opt/deadline/worker/bin/activate
            - pip install --upgrade pip
            
      - name: InstallDeadlineCloudWorkerAgent
        action: ExecuteBash
        inputs:
          commands:
            - source /opt/deadline/worker/bin/activate
            - python -m pip install deadline-cloud-worker-agent
    
      - name: ConfigureDeadlineCloudWorkerAgent
        action: ExecuteBash
        inputs:
          commands:
            - /opt/deadline/worker/bin/install-deadline-worker --farm-id '{{ FarmId }}' --fleet-id '{{ FleetId }}' --allow-shutdown -y
    
      - name: PrepareDeadlineCloudWorkerAgent
        action: ExecuteBash
        inputs:
          commands:
            - systemctl stop deadline-worker
            - systemctl enable deadline-worker
            - rm -rf /var/lib/deadline/*
            - rm -rf /var/log/amazon/deadline/*
  - name: verify
    steps:
      - name: CheckDeadlineWorkerStatus
        action: ExecuteBash
        inputs:
          commands:
            - systemctl status deadline-worker
```
</details>


#### Houdiniのインストールコンポーネント
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492828/f78b6e95-c9ab-e08d-50d9-b65769774df5.png)
<details><summary>コンポーネント定義</summary>

```yaml
name: InstallHoudini
description: Install Houdini on Amazon Linux 2023 using EC2 Image Builder
schemaVersion: 1.0

parameters:
- HoudiniVersion:
    type: string
    description: Houdini Version
- EULA:
    type: string
    description: Date of EULA
- InstallerBucket:
    type: string

phases:
- name: build
  steps:
    - name: InstallDependencies
      action: ExecuteBash
      inputs:
        commands:
          - dnf install mesa-libGLU libXcursor libXScrnSaver -y
          
    - name: InstallHoudini
      action: ExecuteBash
      inputs:
        commands:
          - aws s3 cp s3://{{ InstallerBucket }}/houdini-py39-{{ HoudiniVersion }}-linux_x86_64_gcc11.2.tar.gz .
          - tar -xvf houdini-py39-{{ HoudiniVersion }}-linux_x86_64_gcc11.2.tar.gz
          - ./houdini-py39-{{ HoudiniVersion }}-linux_x86_64_gcc11.2/houdini.install  --accept-EULA {{ EULA }} --auto-install --install-houdini --install-bin-symlink --no-install-license --no-install-menus --install-hfs-symlink

    - name: InstallDeadlineCloudForHoudini
      action: ExecuteBash
      inputs:
        commands:
          - dnf install python3-pip -y
          - pip3 install deadline deadline-cloud-for-houdini
    
    - name: IncreaseMaxStackSize
      action: ExecuteBash
      inputs:
        commands:
          - echo "    *            soft    stack           50000" >> /etc/security/limits.conf
          - echo "    *            hard    stack           50000" >> /etc/security/limits.conf

    - name: Reboot
      action: Reboot
      inputs:
        delaySeconds: 60

- name: verify
  steps:
    - name: VerifyHoudiniInstall
      action: ExecuteBash
      inputs:
        commands:
          - houdini --version
          - hython -c "exit()"
```
</details>




#### イメージレシピの定義
作成した各コンポーネントを関連付けたイメージレシピを作成し、各コンポーネントのパラメーターを設定してマシンイメージの構築を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492828/d48c7cc5-0900-7193-f459-a1bbba71a5a7.png)


このようにイメージビルダーによってワーカーイメージの構築を行うことで、各ファーム、フリートに関連付けられたワーカーエージェントのインストールが行えます。
またインストールするソフトウェアのバージョンをコンポーネント定義のパラメータとして持たせることで、別のバージョンのソフトウェアをインストールする際にイメージレシピを編集するだけで簡単にワーカーのセットアップが可能になります。

## Cloud Formationによるコード管理
上記のImage Builderの設定等を含めて、Deadline Cloudの構築をCloud Formationによって自動化します。
Cloud Formationによって管理する際にテンプレートを以下の2つに分割しています。
- farm.yaml
- queue.yaml

FarmとQueueでテンプレートを分割する理由は、一つのファームに対して複数のキューを作成したいためです。
具体的には開発、本番などの環境ごとでファームを分け、プロジェクトごとでキューを分ける形が良いと思います。
具体的には以下のような構造になります。
- development-farm
    - project-a-development-queue
    - project-b-development-queue
- production-farm
    - project-a-production-queue
    - project-b-production-queue


#### farm.yaml
Farm、ネットワークの構築を行います。Environmentパラメーターによって開発、本番環境を指定するようにします
<details open><summary>Cloud Formationテンプレート</summary>

```yaml: farm.yaml
AWSTemplateFormatVersion: 2010-09-09
Description: Deploy AWS Deadline Cloud Farm Resources

Parameters: 
  Environment:
    Type: String
    AllowedValues:
      - development
      - production

  VpcCIDR:
    Description: Please enter the IP range (CIDR notation) for this VPC
    Type: String
    Default: 10.0.0.0/16

  PublicSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone
    Type: String
    Default: 10.0.10.0/24

  PublicSubnet2CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the second Availability Zone
    Type: String
    Default: 10.0.11.0/24

Mappings:
  EnvironmentMap:
    development:
      DisplayName: Development
      Tag: Dev
    production:
      DisplayName: Production
      Tag: Prod

  RegionMap:
    us-east-1:
      AZs:
        - us-east-1a
        - us-east-1b
        - us-east-1c
    us-west-2:
      AZs:
        - us-west-2a
        - us-west-2b
        - us-west-2c
    ap-northeast-1:
      AZs:
        - ap-northeast-1a
        - ap-northeast-1c
        - ap-northeast-1d

Resources: 
  Farm:
    Type: AWS::Deadline::Farm
    Properties:
      DisplayName: !Sub 
        - DeadlineCloud ${EnvDisplayName} Farm
        - EnvDisplayName: !FindInMap
          - EnvironmentMap
          - !Ref Environment
          - DisplayName

  ############################################################
  # Network
  ############################################################
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags: 
      - Key: Name
        Value: !Sub deadline-cloud-${Environment}-vpc
      - Key: Environment
        Value: !Ref Environment

  IGW:
    Type: AWS::EC2::InternetGateway
    Properties: 
      Tags: 
      - Key: Name
        Value: !Sub deadline-cloud-${Environment}-igw
      - Key: Environment
        Value: !Ref Environment

  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref IGW

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  PublicRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref IGW

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Ref PublicSubnet1CIDR
      AvailabilityZone: !Select
        - 0
        - !FindInMap
          - RegionMap
          - !Ref AWS::Region
          - AZs
      MapPublicIpOnLaunch: true
      Tags: 
      - Key: Name
        Value: !Sub deadline-cloud-${Environment}-worker-public-subnet-a
      - Key: Environment
        Value: !Ref Environment

  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Ref PublicSubnet2CIDR
      MapPublicIpOnLaunch: true
      AvailabilityZone: !Select
        - 1
        - !FindInMap
          - RegionMap
          - !Ref AWS::Region
          - AZs
      Tags: 
      - Key: Name
        Value: !Sub deadline-cloud-${Environment}-worker-public-subnet-c
      - Key: Environment
        Value: !Ref Environment

  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet2

  WorkerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security Group created for deadline workers
      GroupName: WorkerSecurityGroup
      SecurityGroupIngress: 
        - CidrIp: 0.0.0.0/0
          IpProtocol: tcp 
          FromPort: 22
          ToPort: 22
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          IpProtocol: -1
      VpcId: !Ref VPC

  
  ############################################################
  # EC2 Image Builder Components
  ############################################################
  SoftwareInstallerBucket:
    Type: AWS::S3::Bucket

  WorkerAgentBuildComponent:
    Type: AWS::ImageBuilder::Component
    Properties:
      Data: |
        name: InstallDeadlineCloudWorkerAgent
        description: Install Deadline Cloud Worker Agent on Amazon Linux 2023
        schemaVersion: 1.0

        parameters:
          - FarmId:
              type: string
              default: ''
              description: The Deadline Cloud Farm ID
          - FleetId:
              type: string
              default: ''
              description: The Deadline Cloud Fleet ID

        phases:
          - name: build
            steps:
              - name: CreateAndConfigurePythonVenv
                action: ExecuteBash
                inputs:
                  commands:
                    - python3 -m venv /opt/deadline/worker
                    - source /opt/deadline/worker/bin/activate
                    - pip install --upgrade pip
                    
              - name: InstallDeadlineCloudWorkerAgent
                action: ExecuteBash
                inputs:
                  commands:
                    - source /opt/deadline/worker/bin/activate
                    - python -m pip install deadline-cloud-worker-agent
            
              - name: ConfigureDeadlineCloudWorkerAgent
                action: ExecuteBash
                inputs:
                  commands:
                    - /opt/deadline/worker/bin/install-deadline-worker --farm-id '{{ FarmId }}' --fleet-id '{{ FleetId }}' --allow-shutdown -y
            
              - name: PrepareDeadlineCloudWorkerAgent
                action: ExecuteBash
                inputs:
                  commands:
                    - systemctl stop deadline-worker
                    - systemctl enable deadline-worker
                    - rm -rf /var/lib/deadline/*
                    - rm -rf /var/log/amazon/deadline/*
          - name: verify
            steps:
              - name: CheckDeadlineWorkerStatus
                action: ExecuteBash
                inputs:
                  commands:
                    - systemctl status deadline-worker
            
      Name: !Sub deadline-cloud-worker-agent-${Environment}-build
      Platform: Linux
      SupportedOsVersions:
        - Amazon Linux 2023
      Version: 1.0.1
  
  HoudiniBuildComponent:
    Type: AWS::ImageBuilder::Component
    Properties:
      Data: 
        Fn::Sub: 
        - |
          name: InstallHoudini
          description: Install Houdini on Amazon Linux 2023 using EC2 Image Builder
          schemaVersion: 1.0

          parameters:
            - HoudiniVersion:
                type: string
                description: Houdini Version
            - EULA:
                type: string
                description: Date of EULA

          phases:
            - name: build
              steps:
                - name: InstallDependencies
                  action: ExecuteBash
                  inputs:
                    commands:
                      - | 
                        dnf install -y alsa-lib \
                          openssl \
                          dbus-libs \
                          expat \
                          fontconfig \
                          glibc \
                          libatomic \
                          libevent \
                          libglvnd-glx \
                          libglvnd-opengl \
                          libICE \
                          libSM \
                          libX11 \
                          libX11-xcb \
                          libxcb \
                          libXcomposite \
                          libXcursor \
                          libXdamage \
                          libXext \
                          libXfixes \
                          libXi \
                          libxkbcommon \
                          libxkbcommon-x11 \
                          libXrandr \
                          libXrender \
                          libXScrnSaver \
                          libXt \
                          libXtst \
                          libzstd \
                          nspr \
                          nss \
                          nss-util \
                          openldap \
                          pciutils-libs \
                          tbb \
                          xcb-util \
                          xcb-util-image \
                          xcb-util-keysyms \
                          xcb-util-renderutil \
                          xcb-util-wm \
                          zlib
                - name: InstallHoudini
                  action: ExecuteBash
                  inputs:
                    commands:
                      - aws s3 cp s3://${InstallerBucket}/houdini-py39-{{ HoudiniVersion }}-linux_x86_64_gcc11.2.tar.gz .
                      - tar -xvf houdini-py39-{{ HoudiniVersion }}-linux_x86_64_gcc11.2.tar.gz
                      - ./houdini-py39-{{ HoudiniVersion }}-linux_x86_64_gcc11.2/houdini.install  --accept-EULA {{ EULA }} --auto-install --install-houdini --install-bin-symlink --no-install-license --no-install-menus --install-hfs-symlink

                - name: InstallDeadlineCloudForHoudini
                  action: ExecuteBash
                  inputs:
                    commands:
                      - dnf install python3-pip -y
                      - pip3 install deadline deadline-cloud-for-houdini
                
                - name: IncreaseMaxStackSize
                  action: ExecuteBash
                  inputs:
                    commands:
                      - echo "    *            soft    stack           50000" >> /etc/security/limits.conf
                      - echo "    *            hard    stack           50000" >> /etc/security/limits.conf

                - name: LoginAsRoot
                  action: ExecuteBash
                  inputs:
                    commands:
                      - sudo su

            - name: verify
              steps:
                - name: VerifyHoudiniInstall
                  action: ExecuteBash
                  inputs:
                    commands:
                      - houdini --version
                      - hython -c "exit()"
        - InstallerBucket: !Ref SoftwareInstallerBucket
      Name: !Sub houdini-${Environment}-build
      Platform: Linux
      SupportedOsVersions:
        - Amazon Linux 2023
      Version: 1.0.4

  ImageBuilderLogBucket:
    Type: AWS::S3::Bucket
    Properties:
      LifecycleConfiguration:
        Rules:
          - Id: AutoDelete
            Status: Enabled
            ExpirationInDays: 14
  
  ImageBuilderEC2Role:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - ec2.amazonaws.com
          Action:
          - sts:AssumeRole
      Policies:
        - PolicyName: LogBucketAccessPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
            - Effect: Allow
              Action:
              - s3:List*
              Resource:
              - '*'
            - Effect: Allow
              Action:
              - s3:*
              Resource:
                - !Sub arn:aws:s3:::${ImageBuilderLogBucket}
                - !Sub arn:aws:s3:::${ImageBuilderLogBucket}/*
        - PolicyName: InstallerBucketAccessPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
            - Effect: Allow
              Action:
              - s3:*
              Resource:
                - !Sub arn:aws:s3:::${SoftwareInstallerBucket}
                - !Sub arn:aws:s3:::${SoftwareInstallerBucket}/*
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
        - arn:aws:iam::aws:policy/EC2InstanceProfileForImageBuilder
        - arn:aws:iam::aws:policy/AWSDeadlineCloud-WorkerHost

  ImageBuilderInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
        - !Ref ImageBuilderEC2Role

  WorkerInfrastructureConfiguration:
    Type: AWS::ImageBuilder::InfrastructureConfiguration
    Properties:
      InstanceProfileName: !Ref ImageBuilderInstanceProfile
      InstanceTypes:
        - m5.xlarge
      Name: !Sub deadline-cloud-worker-${Environment}-infrastructure-configuration
      SubnetId: !Ref PublicSubnet1
      SecurityGroupIds: 
        - !Ref WorkerSecurityGroup
      TerminateInstanceOnFailure: true
      Logging:
        S3Logs:
          S3BucketName: !Ref ImageBuilderLogBucket

Outputs:
  Farm:
    Value: !GetAtt Farm.FarmId

  VpcId:
    Value: !Ref VPC

  PublicSubnet1Id:
    Value: !Ref PublicSubnet1

  PublicSubnet2Id:
    Value: !Ref PublicSubnet2

  WorkerSecurityGroupId:
    Value: !Ref WorkerSecurityGroup

  WorkerAgentBuildComponent:
    Value: !Ref WorkerAgentBuildComponent

  HoudiniBuildComponent:
    Value: !Ref HoudiniBuildComponent
  
  WorkerInfrastructureConfiguration:
    Value: !Ref WorkerInfrastructureConfiguration
```
</details>

#### queue.yaml
Queue、Fleet、ワーカーイメージの構築を行います。またワーカーのオートスケーリングの設定を行います。

<details open><summary>Cloud Formationテンプレート</summary>

```yaml: queue.yaml
AWSTemplateFormatVersion: 2010-09-09
Description: Deploy AWS Deadline Cloud Queue & Fleet

Parameters: 
  Environment:
    Type: String
    AllowedValues:
      - development
      - staging
      - production

  FarmId:
    Type: String
  
  VpcId:
    Type: AWS::EC2::VPC::Id

  SubnetIds:
    Type: List<AWS::EC2::Subnet::Id>

  WorkerSecurityGroupId:
    Type: AWS::EC2::SecurityGroup::Id
  
  ParentImageId:
    Type: AWS::EC2::Image::Id
    Default: ami-0d03c6e00d5732e28

  WorkerAgentBuildComponent:
    Type: String

  HoudiniBuildComponent:
    Type: String

  HoudiniVersion:
    Type: String
    Default: "20.0.590"

  HoudiniEULA:
    Type: String
    Default: "2021-10-13"
  
  WorkerInfrastructureConfiguration:
    Type: String

Mappings:
  EnvironmentMap:
    development:
      DisplayName: Development
      Tag: Dev
    production:
      DisplayName: Production
      Tag: Prod

Resources: 
  JobAttachmentBucket:
    Type: AWS::S3::Bucket

  QueuePolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - s3:GetObject
              - s3:PutObject
              - s3:ListBucket
              - s3:GetBucketLocation
            Resource: 
              - !Sub arn:aws:s3:::${JobAttachmentBucket}
              - !Sub arn:aws:s3:::${JobAttachmentBucket}/DeadlineCloud/*
            Condition:
              StringEquals:
                aws:ResourceAccount: !Ref AWS::AccountId
          - Effect: Allow
            Action:
              - logs:GetLogEvents
            Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/deadline/${FarmId}/*
  
  QueueRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: 
                - deadline.amazonaws.com
                - credentials.deadline.amazonaws.com
            Action:
              - sts:AssumeRole
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              ArnEquals:
                aws:SourceArn: !Sub arn:aws:deadline:${AWS::Region}:${AWS::AccountId}:farm/${FarmId}
      ManagedPolicyArns:
        - !Ref QueuePolicy

  Queue:
    Type: AWS::Deadline::Queue
    Properties:
      DisplayName: !Sub 
        - ${AWS::StackName} ${EnvDisplayName} Queue
        - EnvDisplayName: !FindInMap
          - EnvironmentMap
          - !Ref Environment
          - DisplayName
      FarmId: !Ref FarmId
      JobAttachmentSettings: 
        RootPrefix: DeadlineCloud
        S3BucketName: !Ref JobAttachmentBucket
      JobRunAsUser: 
        RunAs: WORKER_AGENT_USER
      RoleArn: !GetAtt QueueRole.Arn

  FleetRolePolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - logs:CreateLogStream
            Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/deadline/${FarmId}/*
            Condition:
              ForAnyValue:StringEquals:
                aws:CalledVia:
                  - deadline.amazonaws.com
          - Effect: Allow
            Action:
              - logs:PutLogEvents
              - logs:GetLogEvents
            Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/deadline/${FarmId}/*

  FleetRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: credentials.deadline.amazonaws.com
            Action:
              - sts:AssumeRole
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              ArnEquals:
                aws:SourceArn: !Sub arn:aws:deadline:${AWS::Region}:${AWS::AccountId}:farm/${FarmId}
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AWSDeadlineCloud-FleetWorker
        - !Ref FleetRolePolicy

  Fleet: 
    Type: AWS::Deadline::Fleet
    Properties:
      Configuration: 
        CustomerManaged:
          Mode: NO_SCALING
          WorkerCapabilities: 
            CpuArchitectureType: x86_64 
            MemoryMiB: 
              Max: 32768
              Min: 8192
            OsFamily: LINUX
            VCpuCount: 
              Max: 8
              Min: 4
      DisplayName: !Sub 
        - ${AWS::StackName} ${EnvDisplayName} Fleet
        - EnvDisplayName: !FindInMap
          - EnvironmentMap
          - !Ref Environment
          - DisplayName
      FarmId: !Ref FarmId
      MaxWorkerCount: 10
      MinWorkerCount: 0
      RoleArn: !GetAtt  FleetRole.Arn
  
  QueueFleetAssociation:
    Type: AWS::Deadline::QueueFleetAssociation
    Properties:
      FarmId: !Ref FarmId
      FleetId: !GetAtt  Fleet.FleetId
      QueueId: !GetAtt Queue.QueueId


  
  ############################################################
  # License Endpoint
  ############################################################
  DeadlineLicenseEndpointSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Sub Security Group created for ${AWS::StackName} ${Environment} deadline license endpoint
      GroupName: !Sub ${AWS::StackName}-${Environment}-deadlineLicenseEndpointSecurityGroup
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          IpProtocol: -1
      VpcId: !Ref VpcId

  HoudiniInboundRule:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      IpProtocol: tcp
      FromPort: 1715
      ToPort: 1715
      SourceSecurityGroupId: !Ref WorkerSecurityGroupId
      GroupId: !Ref DeadlineLicenseEndpointSecurityGroup

  MantraInboundRule:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      IpProtocol: tcp
      FromPort: 1717
      ToPort: 1717
      SourceSecurityGroupId: !Ref WorkerSecurityGroupId
      GroupId: !Ref DeadlineLicenseEndpointSecurityGroup

  DeadlineLicenseEndpoint:
    Type: AWS::Deadline::LicenseEndpoint
    Properties:
      SecurityGroupIds: 
        - !Ref DeadlineLicenseEndpointSecurityGroup
      SubnetIds: !Ref SubnetIds
      VpcId: !Ref VpcId
  
  Houdini20MeteredProduct:
        Type: AWS::Deadline::MeteredProduct
        Properties:
          Family: product-houdini
          LicenseEndpointId: !GetAtt DeadlineLicenseEndpoint.LicenseEndpointId
          Port: 1715
          ProductId: houdini-20.0
          Vendor: sidefx
          
  Mantra20MeteredProduct:
        Type: AWS::Deadline::MeteredProduct
        Properties:
          Family: product-mantra
          LicenseEndpointId: !GetAtt DeadlineLicenseEndpoint.LicenseEndpointId
          Port: 1717
          ProductId: mantra-20.0
          Vendor: sidefx


  ############################################################
  # EC2 Image Builder
  ############################################################
  ImageRecipe:
    Type: AWS::ImageBuilder::ImageRecipe
    Properties:
      Components:
        - ComponentArn: !Ref WorkerAgentBuildComponent
          Parameters:
            - Name: FarmId
              Value:  
                - !Ref FarmId
            - Name: FleetId
              Value: 
                - !GetAtt Fleet.FleetId
        - ComponentArn: !Ref HoudiniBuildComponent
          Parameters:
            - Name: HoudiniVersion
              Value: 
                - !Ref HoudiniVersion
            - Name: EULA
              Value: 
                - !Ref HoudiniEULA
      Name: !Sub ${AWS::StackName}-${Environment}-worker-recipe
      ParentImage: !Ref ParentImageId
      BlockDeviceMappings:
        - Ebs:
            VolumeSize: 60
            VolumeType: gp3
          DeviceName: /dev/xvda
      Version: 1.1.1

  Image:
    Type: AWS::ImageBuilder::Image
    Properties:
      ImageRecipeArn: !Ref ImageRecipe
      InfrastructureConfigurationArn: !Ref WorkerInfrastructureConfiguration
      ImageTestsConfiguration:
        ImageTestsEnabled: true
        TimeoutMinutes: 60

  ImageParameter:
    Type: AWS::SSM::Parameter
    Properties:
      DataType: aws:ec2:image
      Name: !Sub ${AWS::StackName}-${Environment}-worker-image-id
      Type: String
      Value: !GetAtt Image.ImageId
      Description: !Sub ${AWS::StackName}-${Environment}-worker-image-id
      
  
  ############################################################
  # Auto Scaling
  ############################################################
  DeadlineCloudWorkerRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /     
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
        - arn:aws:iam::aws:policy/AWSDeadlineCloud-WorkerHost
      Tags:
        - Key: Environmet
          Value: !Ref Environment
        - Key: FarmId
          Value: !Ref FarmId
        - Key: FleetId
          Value: !GetAtt Fleet.FleetId
  
  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref DeadlineCloudWorkerRole

  LaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: !Sub DeadlineCloudWorker-LaunchTemplate-${Fleet.FleetId}
      LaunchTemplateData:
        NetworkInterfaces:
          - DeviceIndex: 0
            AssociatePublicIpAddress: true
            Groups:
              - !Ref WorkerSecurityGroupId
            DeleteOnTermination: true
        ImageId: !Sub '{{resolve:ssm:${ImageParameter}}}'
        InstanceInitiatedShutdownBehavior: terminate
        IamInstanceProfile:
          Arn: !GetAtt InstanceProfile.Arn
        MetadataOptions:
          HttpTokens: required
          HttpEndpoint: enabled
        TagSpecifications: 
          - ResourceType: instance
            Tags:
              - Key: Name
                Value: !Sub DeadlineCloudWorker-${Fleet.FleetId}
              - Key: Environmet
                Value: !Ref Environment
              - Key: FarmId
                Value: !Ref FarmId
              - Key: FleetId
                Value: !GetAtt Fleet.FleetId
        UserData:
          Fn::Base64:
            Fn::Sub:
              - |
                #!/bin/bash
                /opt/hfs${HoudiniVersion}/bin/hserver -S "http://${LicenseEndpointDnsName}:1715;http://${LicenseEndpointDnsName}:1717"
              - LicenseEndpointDnsName: !GetAtt  DeadlineLicenseEndpoint.DnsName
                HoudiniVersion: !Ref HoudiniVersion
      TagSpecifications:
        - ResourceType: launch-template
          Tags:
            - Key: Environmet
              Value: !Ref Environment
            - Key: FarmId
              Value: !Ref FarmId
            - Key: FleetId
              Value: !GetAtt Fleet.FleetId

  AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AutoScalingGroupName: !Sub DeadlineCloudWorker-AutoScalingGroup-${Fleet.FleetId}
      MinSize: 0
      MaxSize: 10
      VPCZoneIdentifier: !Ref SubnetIds
      NewInstancesProtectedFromScaleIn: true
      MixedInstancesPolicy:
        InstancesDistribution:
          OnDemandBaseCapacity: 0
          OnDemandPercentageAboveBaseCapacity: 0
          SpotAllocationStrategy: capacity-optimized
          OnDemandAllocationStrategy: lowest-price
        LaunchTemplate:
          LaunchTemplateSpecification:
            LaunchTemplateId: !Ref LaunchTemplate
            Version: !GetAtt LaunchTemplate.LatestVersionNumber
          Overrides:
            - InstanceType: m5.large
            - InstanceType: m5d.large
            - InstanceType: m5a.large
            - InstanceType: m5ad.large
            - InstanceType: m5n.large
            - InstanceType: m5dn.large
            - InstanceType: m4.large
            - InstanceType: m3.large
            - InstanceType: r5.large
            - InstanceType: r5d.large
            - InstanceType: r5a.large
            - InstanceType: r5ad.large
            - InstanceType: r5n.large
            - InstanceType: r5dn.large
            - InstanceType: r4.large  
      MetricsCollection:
        - Granularity: 1Minute
          Metrics:
            - GroupMinSize
            - GroupMaxSize
            - GroupDesiredCapacity
            - GroupInServiceInstances
            - GroupTotalInstances
            - GroupInServiceCapacity
            - GroupTotalCapacity

  AutoScalingLambda:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |-
          """
          This lambda is configured to handle "Fleet Size Recommendation Change"
          messages. It will handle all such events, and requires
          that the ASG is named based on the fleet id. It will scale up/down the fleet
          based on the recommended fleet size in the message.
          
          Example EventBridge message:
          {
              "version": "0",
              "id": "6a7e8feb-b491-4cf7-a9f1-bf3703467718",
              "detail-type": "Fleet Size Recommendation Change",
              "source": "aws.deadline",
              "account": "111122223333",
              "time": "2017-12-22T18:43:48Z",
              "region": "us-west-1",
              "resources": [],
              "detail": {
                  "farmId": "farm-12345678900000000000000000000000",
                  "fleetId": "fleet-12345678900000000000000000000000",
                  "oldFleetSize": 1,
                  "newFleetSize": 5,
              }
          }
          """
          
          import json
          import boto3
          import logging

          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          auto_scaling_client = boto3.client("autoscaling")

          def lambda_handler(event, context):
              logger.info(event)
              event_detail = event["detail"]
              fleet_id = event_detail["fleetId"]
              desired_capacity = event_detail["newFleetSize"]

              asg_name = f"DeadlineCloudWorker-AutoScalingGroup-{fleet_id}"
              auto_scaling_client.set_desired_capacity(
                  AutoScalingGroupName=asg_name,
                  DesiredCapacity=desired_capacity,
                  HonorCooldown=False,
              )

              return {
                  'statusCode': 200,
                  'body': json.dumps(f'Successfully set desired_capacity for {asg_name} to {desired_capacity}')
              }
      Handler: index.lambda_handler
      Role: !GetAtt  AutoScalingLambdaServiceRole.Arn
      Runtime: python3.11
    DependsOn:
      - AutoScalingLambdaServiceRoleDefaultPolicy
      - AutoScalingLambdaServiceRole
  AutoScalingEventRule:
    Type: AWS::Events::Rule
    Properties:
      EventPattern:
        source:
          - aws.deadline
        detail-type:
          - Fleet Size Recommendation Change
      State: ENABLED
      Targets:
        - Arn: !GetAtt AutoScalingLambda.Arn
          DeadLetterConfig:
            Arn: !GetAtt UnprocessedAutoScalingEventQueue.Arn
          Id: Target0
          RetryPolicy:
            MaximumRetryAttempts: 15
  AutoScalingEventRuleTargetPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt AutoScalingLambda.Arn
      Principal: events.amazonaws.com
      SourceArn: !GetAtt AutoScalingEventRule.Arn
  AutoScalingLambdaServiceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
        Version: 2012-10-17
      ManagedPolicyArns:
        - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
  AutoScalingLambdaServiceRoleDefaultPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyDocument:
        Statement:
          - Action: autoscaling:SetDesiredCapacity
            Effect: Allow
            Resource: '*'
        Version: 2012-10-17
      PolicyName: AutoScalingLambdaServiceRoleDefaultPolicy
      Roles:
        - !Ref AutoScalingLambdaServiceRole
  UnprocessedAutoScalingEventQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: deadline-unprocessed-autoscaling-events
    UpdateReplacePolicy: Delete
    DeletionPolicy: Delete
  UnprocessedAutoScalingEventQueuePolicy:
    Type: AWS::SQS::QueuePolicy
    Properties:
      PolicyDocument:
        Statement:
          - Action: sqs:SendMessage
            Condition:
              ArnEquals:
                'aws:SourceArn': !GetAtt AutoScalingEventRule.Arn
            Effect: Allow
            Principal:
              Service: events.amazonaws.com
            Resource: !GetAtt UnprocessedAutoScalingEventQueue.Arn
        Version: 2012-10-17
      Queues:
        - !Ref UnprocessedAutoScalingEventQueue

Outputs:
  QueueId:
    Value: !GetAtt Queue.QueueId
    
  FleetId:
    Value: !GetAtt Fleet.FleetId

  ImageId:
    Value: !Sub '{{resolve:ssm:${ImageParameter}}}'

  LicenseEndpointDnsName:
    Value: !GetAtt DeadlineLicenseEndpoint.DnsName
```
</details>

## 開発環境のデプロイ
Farmのスタックをデプロイします
AWSコンソールでCloudFormationを開き、スタックの作成をクリックします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492828/2c812786-3d65-b041-28cc-08430240799e.png)

farm.yamlをアップロードします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492828/e9384f38-7315-0b10-2e85-2c37178fdee8.png)

スタック名は何でもよいですが、DevelopmentFarmStackなどとして、Environmentはdevelopmentを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492828/48b5225b-0be7-860c-4eaa-bcb88d6f66a8.png)

イベントタブから進捗を確認できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492828/d137043b-d5df-d97a-41ed-49cee617db15.png)

出力タブにqueueスタックが入力として求めるパラメーターが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492828/1e3a834e-551e-6122-70f0-2a212b9debb8.png)

S3に移動してSoftwareInstallerBucketをさがしてHoudiniのインストールしたいバージョンのインストーラーをアップロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492828/7f725422-7f9f-2dc8-637d-7029ba794ad9.png)


別タブでQueueStackを作成します。Stack名は{ProjectName}DevelopmentQueueStackなどとして、各パラメーター値をFarmStackの出力タブを確認して入力してください。 HoudiniVersionはアップロードしたーインストーラのバージョンと同じものを指定します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492828/a3500877-94c3-60ea-32c4-ec2b29038e62.png)

イメージビルダーのページからワーカーイメージが構築中であることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492828/ad46186a-f610-7c09-09e7-4c21e307b040.png)


またログストリームからインストールログが確認できます。


またMonitorも作成しておきます。CloudFormationで書いてもいいですが、Webコンソールから簡単に設定できるためブラウザから設定します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492828/97f2d83e-20e0-0c79-7b4a-8d09a158e4d2.png)


## ジョブのサブミット＆確認
サブミットについては本記事では詳しく触れませんが、DeadlineCloudサブミッターをインストールして`C:\Users\ユーザー名\Documents\houdini20.0\packages`にjsonファイルを配置します。
```json:deadline_submitter_for_houdini.json
{
    "env": [
        {
            "DEADLINE_CLOUD_FOR_HOUDINI": "C:\Users\ユーザー名\DeadlineCloudSubmitter/Submitters/Houdini"
        },
        {
            "PYTHONPATH": "$DEADLINE_CLOUD_FOR_HOUDINI/python"
        }
    ],
    "hpath": "$DEADLINE_CLOUD_FOR_HOUDINI"
}

```

Houdiniでサブミット用のシーンをセットアップしてジョブをサブミットします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492828/9ad87018-fe34-6a1c-a795-a3745b633c7e.png)

Monitorからジョブの進捗を確認できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492828/d3951dd1-b63d-7afe-bc13-7886e156d0f6.png)

Download Outputからジョブ結果のダウンロードができます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492828/e10bce00-8128-7d24-ba89-146dc34b1f01.png)

## まとめ
以上になります。

GUNCY'SではAWSを利用したクラウドパイプラインの構築・運用のサポートを行っています。
導入に際してのサポートが必要な場合は、ぜひお気軽にご連絡ください。