😳

AppStream 2.0 Desktop Viewを起動してみてVDIとしての所感

2020/11/03に公開

想定読者

  • VDI基盤の運営者
    • 複数人のユーザに対して、WindowsのRDP基盤を提供している方

AppStreamとは...の前に

アプリケーション仮想化

Citrix に代表されるように、
以前はOn-premにあった基幹系システム要件で、どうしても稼働しているアプリケーションのバージョンアップができない、
だけど、サポート期限が切れてしまった、といった延命措置のために利用されるケースが多かったのではないかと思います。

今はクラウドにシステム稼働基盤がリフト、リフトアンドシフトした関係で、
認証・認可を行ったユーザが取り扱うことのアプリケーションを制限したいといったような使い方の変化があるのかなと思います。

AppStream 2.0とは

1.0はないの?の疑問は、こちらのブログをご覧ください。
私も参考にさせていただいて、へぇと思った一人です。

参考1:AWS再入門ブログリレー Amazon AppStream 2.0 編
参考2:[アップデート] AppStream 2.0 にネイティブデスクトップエクスペリエンスのサポートが追加されました

2020年8月のサービスアップデートで何があったのか

以下のアナウンスが行われています。

Amazon AppStream 2.0 にネイティブデスクトップエクスペリエンスのサポートが追加されました

これにより、AWS の VDI サービスの単純に選択肢が増えました。
今までは、Amazon Workspace がNGだった場合は、
EC2でWindows Serverを作成して、RDP接続する形しか選択肢はありませんでした。

このサービスアップデートにより、複数人で利用する際のVDI基盤の選択肢が広がったのかなと思います。

ということで、少し自分でも所管を知りたく3時間程度で環境作ってみました。

システム管理者の観点で試してみた

前述で参考にさせていただいたクラスメソッドさんのブログでは触れられていなかった点について、
システム管理者の方が気にするだろうなという視点で見てみました。

CloudFormationで実験環境構築

CloudFormationで以下のテンプレートを使って作成しています。

テスト環境作成のCloudFormation

AWSTemplateFormatVersion: "2010-09-09"
Description: 
  VPC and Subnet Create

Metadata: 
  "AWS::CloudFormation::Interface": 
    ParameterGroups: 
      - Label: 
          default: "Project Name Prefix"
        Parameters: 
          - PJPrefix
      - Label: 
          default: "Network Configuration"
        Parameters: 
          - VPCCIDR
          - PublicSubnetCCIDR
          - PublicSubnetDCIDR
          - PrivateSubnetCCIDR
          - PrivateSubnetDCIDR
    ParameterLabels: 
      VPCCIDR: 
        default: "VPC CIDR"
      PublicSubnetCCIDR: 
        default: "PublicSubnetC CIDR"
      PublicSubnetDCIDR: 
        default: "PublicSubnetD CIDR"
      PrivateSubnetCCIDR: 
        default: "PrivateSubnetC CIDR"
      PrivateSubnetDCIDR: 
        default: "PrivateSubnetD CIDR"

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------# 
Parameters:
  PJPrefix:
    Type: String

  VPCCIDR:
    Type: String
    Default: "10.0.0.0/16"

  PublicSubnetCCIDR:
    Type: String
    Default: "10.0.1.0/24"

  PublicSubnetDCIDR:
    Type: String
    Default: "10.0.2.0/24"

  PrivateSubnetCCIDR:
    Type: String
    Default: "10.0.11.0/24"

  PrivateSubnetDCIDR:
    Type: String
    Default: "10.0.12.0/24"

Resources: 
# ------------------------------------------------------------#
#  VPC
# ------------------------------------------------------------#
# VPC Create
  VPC: 
    Type: "AWS::EC2::VPC"
    Properties: 
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: "true"
      EnableDnsHostnames: "true"
      InstanceTenancy: default
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-vpc"

# InternetGateway Create
  InternetGateway: 
    Type: "AWS::EC2::InternetGateway"
    Properties: 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-igw"

# IGW Attach
  InternetGatewayAttachment: 
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties: 
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC 

# ------------------------------------------------------------#
#  Subnet
# ------------------------------------------------------------#          
# Public SubnetC Create
  PublicSubnetC: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-1c"
      CidrBlock: !Ref PublicSubnetCCIDR
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-public-subnet-c"

# Public SubnetD Create
  PublicSubnetD: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-1d"
      CidrBlock: !Ref PublicSubnetDCIDR
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-public-subnet-d"

# Private SubnetC Create
  PrivateSubnetC: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-1c"
      CidrBlock: !Ref PrivateSubnetCCIDR
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-private-subnet-c"

# Private SubnetD Create
  PrivateSubnetD: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-1d"
      CidrBlock: !Ref PrivateSubnetDCIDR
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-private-subnet-d"

# ------------------------------------------------------------#
#  RouteTable
# ------------------------------------------------------------#          
# Public RouteTableC Create
  PublicRouteTableC: 
    Type: "AWS::EC2::RouteTable"
    Properties: 
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-public-route-c"

# Public RouteTableD Create
  PublicRouteTableD: 
    Type: "AWS::EC2::RouteTable"
    Properties: 
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-public-route-d"

# Private RouteTableC Create
  PrivateRouteTableC: 
    Type: "AWS::EC2::RouteTable"
    Properties: 
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-private-route-c"

# Private RouteTableD Create
  PrivateRouteTableD: 
    Type: "AWS::EC2::RouteTable"
    Properties: 
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-private-route-d"

# ------------------------------------------------------------#
# Routing
# ------------------------------------------------------------# 
# PublicRouteC Create
  PublicRouteC: 
    Type: "AWS::EC2::Route"
    Properties: 
      RouteTableId: !Ref PublicRouteTableC 
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway 

# PublicRouteD Create
  PublicRouteC: 
    Type: "AWS::EC2::Route"
    Properties: 
      RouteTableId: !Ref PublicRouteTableD 
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway 

# ------------------------------------------------------------#
# RouteTable Associate
# ------------------------------------------------------------# 
# PublicRouteTable Associate SubnetC
  PublicSubnetARouteTableAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref PublicSubnetC 
      RouteTableId: !Ref PublicRouteTableC

# PublicRouteTable Associate SubnetD
  PublicSubnetARouteTableAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref PublicSubnetD 
      RouteTableId: !Ref PublicRouteTableD

# PrivateRouteTable Associate SubnetC
  PrivateSubnetARouteTableAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref PrivateSubnetC
      RouteTableId: !Ref PrivateRouteTableC

# PrivateRouteTable Associate SubnetD
  PrivateSubnetCRouteTableAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref PrivateSubnetD
      RouteTableId: !Ref PrivateRouteTableD

# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#                
Outputs:
# VPC
  VPC:
    Value: !Ref VPC
    Export:
      Name: !Sub "${PJPrefix}-vpc"

  VPCCIDR:
    Value: !Ref VPCCIDR
    Export:
      Name: !Sub "${PJPrefix}-vpc-cidr"

# Subnet
  PublicSubnetC:
    Value: !Ref PublicSubnetC
    Export:
      Name: !Sub "${PJPrefix}-public-subnet-c"

  PublicSubnetCCIDR:
    Value: !Ref PublicSubnetCCIDR
    Export:
      Name: !Sub "${PJPrefix}-public-subnet-c-cidr"

  PublicSubnetD:
    Value: !Ref PublicSubnetD
    Export:
      Name: !Sub "${PJPrefix}-public-subnet-d"

  PublicSubnetDCIDR:
    Value: !Ref PublicSubnetDCIDR
    Export:
      Name: !Sub "${PJPrefix}-public-subnet-d-cidr"

  PrivateSubnetC:
    Value: !Ref PrivateSubnetC
    Export:
      Name: !Sub "${PJPrefix}-private-subnet-c"

  PrivateSubnetCCIDR:
    Value: !Ref PrivateSubnetCCIDR
    Export:
      Name: !Sub "${PJPrefix}-private-subnet-c-cidr"

  PrivateSubnetD:
    Value: !Ref PrivateSubnetD
    Export:
      Name: !Sub "${PJPrefix}-private-subnet-d"

  PrivateSubnetDCIDR:
    Value: !Ref PrivateSubnetDCIDR
    Export:
      Name: !Sub "${PJPrefix}-private-subnet-d-cidr"

# Route
  PublicRouteTableC:
    Value: !Ref PublicRouteTableC
    Export:
      Name: !Sub "${PJPrefix}-public-route-c"

  PublicRouteTableD:
    Value: !Ref PublicRouteTableD
    Export:
      Name: !Sub "${PJPrefix}-public-route-d"

  PrivateRouteTableC:
    Value: !Ref PrivateRouteTableC
    Export:
      Name: !Sub "${PJPrefix}-private-route-c"

  PrivateRouteTableD:
    Value: !Ref PrivateRouteTableD
    Export:
      Name: !Sub "${PJPrefix}-private-route-d"

使用可能なベースイメージについて

2020年11月3日時点では、Windows Server 2012 R2, 2016, 2019 の3種類のみとなっています。

OSベースイメージ

ClientOSに依存するアプリケーションを使っている方は、Amazon Workspace のみ選択肢となります。
参照: 自分の Windows デスクトップライセンスを使用する

デスクトップにあるImageAssistant(※)

ユーザプロファイルの保存について

AppStream 2.0 Desktop Viewは、インスタンス毎にユーザを紐付けるAmazon Workspaceと違い、
AutoScalling Policyで定義されているインスタンスをアクセスユーザで共有する仕様のため、
ユーザプロファイルは、インスタンス内部には保存できません。
一方で、S3 Bucket / Google Drive / OneDrive といった選択が可能となっています。

ユーザプロファイル

また、S3にユーザプロファイルを設定する場合、自動でS3 Bucketが作成されるうえに、
不意の削除を防ぐ、バケットポリシーも設定されていました。
(一通り検証終わって、削除するときにバケットポリシーの存在に気づきました。)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PreventAccidentalDeletionOfBucket",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:DeleteBucket",
            "Resource": "arn:aws:s3:::${S3-bucket-name}"
        }
    ]
}

ユーザ設定について

VDIサービスを使っているとシステム管理者の方が気にするであろうセキュリティの設定です。
以下の3点について設定可能です。

コピーアンドペーストの制限

双方向、あるいは単一方向(Local → Remote , Remote → Local )の設定となります。

コピーアンドペースト

ファイルのアップロード、ダウンロードの制限

双方向、あるいは単一方向(Local → Remote , Remote → Local )の設定となります。

ファイルの送受信

プリンタの制限

Localにprint許可をする設定となります。

引用: Get Started with Amazon AppStream 2.0: Set Up With Sample Applications

When they choose Print in the application, they can download a .pdf file that they can print to a local printer.
You can disable this option to prevent users from printing to a local device.

プリンタ

ログオフ

画面上部にAppStream 2.0のツールバーが表示され、そちらからログオフをする形となるため、
いつもどおりのログオフ手順と異なる点はユーザに伝える必要はあるかと思います。
事前に伝えないと、問い合わせに繋がるかなと...

電源オプション

ログオフ

ログオフ確認画面

その他

Image作成する際に、予めデスクトップに用意されているImageAssistを利用するのですが、
私の場合、DesktopViewを試すことを目的としていたので、
ショートアイコンを削除してしまい、実態がどこにいるのか焦りましたので、
同じ事態に陥る方のために2020年11月3日時点のパスを記載します。

C:\Program Files\Amazon\Photon\AppCatalogHelper\PhotonWindowsAppCatalogHelper.exe

所感

今回、まずはと思い3時間程度で、カスタムイメージの作成からFleetの作成までを行いました。

Amazon Workspaceの場合、前述の通り、ユーザをインスタンスに紐づけてしまうため、
全体のアカウント数に対して、アクティブユーザ数が少ない場合のVDIサービス基盤としての利用としてはいいのかなと思いました。

例えばシフト勤務などを行っている場合など、同時利用者数がアカウント数に対して少なくなると思いますので、
AppStrem 2.0 Desktop Viewを提供するといった形です。

また、50人程度の規模であれば、Amazon Cognitoで内部ユーザとして定義可能ですが、その場合 Photon Userと固定値になり、
ドメイン参加している場合は、個別のユーザ名になることも、事前に伝える必要があるかなと思います。

• 非ドメイン結合インスタンス: C:\Users\PhotonUser\My Files\Homeフォルダ
• ドメイン参加インスタンス: C:\Users\%username%\My Files\Homeフォルダ

引用: Amazon AppStream 2.0 管理ガイド

Discussion