Amazon DCV でとりあえず検証用 Amazon Linux 2023 GUI 環境にアクセスする

こんにちは、広野です。

検証で Amazon Linux 2023 のデスクトップ環境 (GUI) が必要になり、最新ソリューションを調べてたところ Amazon DCV を使ってみたくなったので環境をつくってみました。

Amazon DCV について

私が説明するまでもなく AWS 公式サイトに説明があるので、そちらを紹介します。もともと NICE-DCV という名前で開発されていたツールを AWS が買収したものです。

 

そして、構築の仕方も re:Post にありますので参考にしました。

 

作成した環境

  • パブリックサブネットの Amazon Linux 2023 インスタンスに Amazon DCV をインストールしています。
  • セキュリティグループで、指定したソース IP アドレスからのみアクセスできるようにします。
  • ログインユーザー名は ec2-user、初期パスワードは AWS Secrets Manager で生成します。
  • Amazon EC2 インスタンスには Elastic IP アドレスを割り当て、アクセスする URL が固定になるようにします。
  • AWS CloudFormation で一発デプロイします。Amazon DCV のインストールは userdata スクリプト内で自動実行します。
  • 既存 VPC を使用する前提です。
  • ポートを 443 にしたかったんですが、QUIC というプロトコル (UDPを使用、1000番以上のポート使用を推奨) が使えるとレスポンスが向上するようだったので、今回はデフォルトのままにしました。

デプロイすると、AWS CloudFormation スタックの出力欄に URL が表示されます。単純に、Elastic IP に HTTPS 8443 番ポートでアクセスするだけのものです。

ブラウザでアクセスすると、ログイン画面が表示されます。

初期パスワードは AWS Secrets Manager で生成されるので、マネジメントコンソールから参照します。ログイン後、パスワード変更してもらえるとより安心です。

無事ログインできると、Amazon Linux 2023 のデスクトップ画面が表示されます。これで検証できそうです!

 

AWS CloudFormation テンプレート

  • パラメータとして、既存の VPC、サブネットを選択できるようにしています。
  • 自分の IAM ユーザー名をパラメータとして指定します。AWS Secrets Manager の閲覧権限に使用されます。
  • インスタンスタイプは Amazon DSV の要件上最低でも medium 以上にする必要があるので t3.medium 一択にしています。
  • EBS のボリュームサイズ、OS のタイムゾーンをパラメータで指定できるようにしています。
AWSTemplateFormatVersion: "2010-09-09"
Description: The CloudFormation template that creates a remote-accessible EC2 instance with Amazon DCV.

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
  SystemName:
    Type: String
    Description: System name. use lower case only. (e.g. example)
    Default: example
    MaxLength: 10
    MinLength: 1

  SubName:
    Type: String
    Description: System sub name. use lower case only. (e.g. prod or dev)
    Default: dev
    MaxLength: 10
    MinLength: 1

  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: Choose a existing VPC ID you deploy the EC2 instance in.

  InstanceSubnet:
    Type: AWS::EC2::Subnet::Id
    Description: Choose an existing Public Subnet ID you deploy the EC2 instance in.

  InstanceType:
    Type: String
    Description: EC2 instance type for dcv-server.
    Default: t3.medium
    AllowedValues:
      - t3.medium

  InstanceVolumeSize:
    Type: Number
    Description: The volume size in GB
    Default: 10

  AllowedSubnet:
    Type: String
    Description: Allowed source IPv4 subnet and subnet mask. (e.g. xxx.xxx.xxx.xxx/32)
    Default: xxx.xxx.xxx.xxx/32
    MaxLength: 18
    MinLength: 9

  YourIamUserName:
    Type: String
    Description: Your IAM user name. This is used for the permission of your dcv-server credential.
    Default: youriamusername
    MaxLength: 30
    MinLength: 1

  TimeZone:
    Type: String
    Description: The specified time zone.
    Default: Asia/Tokyo
    MaxLength: 30
    MinLength: 1

Resources:
# ------------------------------------------------------------#
# EC2 Launch template
# ------------------------------------------------------------#
  EC2LaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: !Sub dcv-server-${SystemName}-${SubName}
      LaunchTemplateData:
        InstanceType: !Ref InstanceType
        ImageId: >-
          {{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64}}
        BlockDeviceMappings:
          - Ebs:
              VolumeSize: !Ref InstanceVolumeSize
              VolumeType: gp3
              DeleteOnTermination: true
              Encrypted: true
            DeviceName: /dev/xvda
        NetworkInterfaces:
          - SubnetId: !Ref InstanceSubnet
            Groups:
              - !Ref Ec2SecurityGroup
            DeviceIndex: 0
            AssociatePublicIpAddress: false
        MetadataOptions:
          HttpTokens: required
        Monitoring:
          Enabled: true
        TagSpecifications:
          - ResourceType: volume
            Tags:
              - Key: Cost
                Value: !Sub ${SystemName}-${SubName}
    DependsOn:
      - Ec2SecurityGroup

# ------------------------------------------------------------#
# EC2 Security Group
# ------------------------------------------------------------#
  Ec2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VpcId
      GroupDescription: Allow dcv-server access via ALB only
      SecurityGroupIngress:
        - Description: Allow HTTPS from clients
          FromPort: 8443
          IpProtocol: tcp
          ToPort: 8443
          CidrIp: !Ref AllowedSubnet
        - Description: Allow QUIC from clients
          FromPort: 8443
          IpProtocol: udp
          ToPort: 8443
          CidrIp: !Ref AllowedSubnet
      SecurityGroupEgress:
        - Description: Allow all outbound traffic
          IpProtocol: -1
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}
        - Key: Name
          Value: !Sub SG-dcv-server-${SystemName}-${SubName}

# ------------------------------------------------------------#
# EC2 Role / Instance Profile (IAM)
# ------------------------------------------------------------#
  Ec2Role:
    Type: AWS::IAM::Role
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W11
            reason: CodeWhisperer requires '*' as a resource, reference https://docs.aws.amazon.com/codewhisperer/latest/userguide/cloud9-setup.html#codewhisperer-IAM-policies
    Properties:
      RoleName: !Sub Ec2Role-dcv-server-${SystemName}-${SubName}
      Description: This role allows EC2 instance to invoke S3 and SSM.
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
        - arn:aws:iam::aws:policy/AWSCodeCommitPowerUser
        - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy
      Policies:
        - PolicyName: !Sub QDeveloperPolicy-${SystemName}-${SubName}
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Action:
                  - "codewhisperer:GenerateRecommendations"
                Resource: "*"
                Effect: Allow
        - PolicyName: !Sub Ec2SecretsPolicy-${SystemName}-${SubName}
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - secretsmanager:GetResourcePolicy
                  - secretsmanager:GetSecretValue
                  - secretsmanager:DescribeSecret
                  - secretsmanager:ListSecretVersionIds
                Resource:
                  - !GetAtt SecretDcvServer.Id
        - PolicyName: !Sub Ec2S3Policy-${SystemName}-${SubName}
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "s3:GetObject"
                Resource:
                  - !Sub "arn:aws:s3:::dcv-license.${AWS::Region}/*"
    DependsOn:
      - SecretDcvServer

  Ec2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: !Ref Ec2Role
      Path: /
      Roles:
        - !Ref Ec2Role
    DependsOn:
      - Ec2Role

# ------------------------------------------------------------#
# Secrets Manager
# ------------------------------------------------------------#
  SecretDcvServer:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: !Sub dcv-server-${SystemName}-${SubName}
      Description: dcv-server credential
      GenerateSecretString:
        PasswordLength: 8
        ExcludePunctuation: true
        IncludeSpace: false
        RequireEachIncludedType: true
      Tags:
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}

  SecretDcvServerResourcePolicy:
    Type: AWS::SecretsManager::ResourcePolicy
    Properties:
      BlockPublicPolicy: true
      SecretId: !Ref SecretDcvServer
      ResourcePolicy:
        Version: "2012-10-17"
        Statement:
          - Effect: Deny
            Principal: "*"
            Action: secretsmanager:GetSecretValue
            Resource: "*"
            Condition:
              StringNotEquals:
                aws:PrincipalArn:
                  - !Sub "arn:aws:iam::${AWS::AccountId}:user/${YourIamUserName}"
                  - !GetAtt Ec2Role.Arn

# ------------------------------------------------------------#
# Elastic IP address for EC2 instance
# ------------------------------------------------------------#
  EipEc2:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}
        - Key: Name
          Value: !Sub eip-dcv-server-${SystemName}-${SubName}

# ------------------------------------------------------------#
# EIP Association
# ------------------------------------------------------------#
  EIPAssociation:
    Type: AWS::EC2::EIPAssociation
    Properties:
      AllocationId: !GetAtt EipEc2.AllocationId
      InstanceId: !Ref Ec2Instance
    DependsOn:
      - Ec2Instance
      - EipEc2

# ------------------------------------------------------------#
# EC2
# ------------------------------------------------------------#
  Ec2Instance:
    Type: AWS::EC2::Instance
    Properties:
      IamInstanceProfile: !Ref Ec2InstanceProfile
      LaunchTemplate:
        LaunchTemplateId: !Ref EC2LaunchTemplate
        Version: !GetAtt EC2LaunchTemplate.LatestVersionNumber
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          set -euxo pipefail
          timedatectl set-timezone ${TimeZone}
          # Install packages
          dnf update -y
          dnf groupinstall "Desktop" -y
          # Setup GDM
          sed -i '/^\[daemon\]/a WaylandEnable=false' /etc/gdm/custom.conf
          systemctl set-default graphical.target
          # Get credential from Secrets Manager and set ec2-user password
          PASSWORD=$(aws secretsmanager get-secret-value --secret-id "${SecretDcvServer.Id}" --region "${AWS::Region}" --query SecretString --output text)
          echo "ec2-user:${!PASSWORD}" | chpasswd
          sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config
          systemctl restart sshd
          # Install dcv-server
          cd /tmp
          rpm --import https://d1uj6qtbmh3dt5.cloudfront.net/NICE-GPG-KEY
          curl -L -O https://d1uj6qtbmh3dt5.cloudfront.net/nice-dcv-amzn2023-$(arch).tgz
          tar -xvzf nice-dcv-amzn2023-$(arch).tgz && cd nice-dcv-*-amzn2023-$(arch)
          dnf install -y ./nice-dcv-server-*.rpm
          dnf install -y ./nice-dcv-web-viewer-*.rpm
          dnf install -y ./nice-xdcv-*.rpm
          systemctl enable dcvserver
          sed -i "/^\[session-management\/automatic-console-session/a owner=\"ec2-user\"\nstorage-root=\"%home%\"" /etc/dcv/dcv.conf
          sed -i "s/^#create-session/create-session/g" /etc/dcv/dcv.conf
          dnf install -y cups
          usermod -a -G sys dcv
          systemctl enable --now cups
          dnf install -y xorg-x11-drv-dummy
          tee /etc/X11/xorg.conf > /dev/null << EOF
          Section "Device"
              Identifier "DummyDevice"
              Driver "dummy"
              Option "UseEDID" "false"
              VideoRam 512000
          EndSection
          Section "Monitor"
              Identifier "DummyMonitor"
              HorizSync   5.0 - 1000.0
              VertRefresh 5.0 - 200.0
              Option "ReducedBlanking"
          EndSection
          Section "Screen"
              Identifier "DummyScreen"
              Device "DummyDevice"
              Monitor "DummyMonitor"
              DefaultDepth 24
              SubSection "Display"
                  Viewport 0 0
                  Depth 24
                  Virtual 4096 2160
              EndSubSection
          EndSection
          EOF
          if [ -f /usr/bin/nvidia-xconfig ]; then
            /usr/bin/nvidia-xconfig --preserve-busid --enable-all-gpus
            dnf install -y vulkan-tools glx-utils
          fi
          reboot
      Tags:
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}
        - Key: Name
          Value: !Sub dcv-server-${SystemName}-${SubName}
    DependsOn:
      - Ec2InstanceProfile

# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#
Outputs:
# EIP
  DcvServerUrl:
    Value: !Sub "https://${EipEc2.PublicIp}:8443"

 

まとめ

いかがでしたでしょうか?

ベタに Amazon EC2 インスタンスをインターネットからアクセスさせる環境になっているのはイケてないので、Amazon DCV がインストールされたプライベートサブネットの EC2 インスタンスに対して Fleet Manager 経由のリモートアクセスをサポートしてくれるような、AWS のアップデートを期待しています。

本記事が皆様のお役に立てれば幸いです。

著者について
広野 祐司

AWS サーバーレスアーキテクチャを駆使して社内クラウド人材育成アプリとコンテンツづくりに勤しんでいます。React で SPA を書き始めたら快適すぎて、他の言語には戻れなくなりました。サーバーレス & React 仲間を増やしたいです。AWS は好きですが、それよりもバックエンド構築を簡単にしてくれたことに対する感謝の気持ちの方が強いです。
取得資格:AWS 認定は15資格、IT サービスマネージャ、ITIL v3 Expert 等
2020 - 2024 Japan AWS Top Engineer 受賞
2022 - 2024 AWS Ambassador 受賞
2023 当社初代フルスタックエンジニア認定
好きなAWSサービス:AWS Amplify / AWS AppSync / Amazon Cognito / AWS Step Functions / AWS CloudFormation

広野 祐司をフォローする

クラウドに強いによるエンジニアブログです。

SCSKクラウドサービス(AWS)は、企業価値の向上につながるAWS 導入を全面支援するオールインワンサービスです。AWS最上位パートナーとして、多種多様な業界のシステム構築実績を持つSCSKが、お客様のDX推進を強力にサポートします。

AWSクラウド
シェアする
タイトルとURLをコピーしました