UiPath Automation Suiteインストール手順 ～ Amazon EKS編

# はじめに
- 本記事では **UiPath Automation Suite v2024.10** をAmazon EKS環境 (東京リージョン) にて、Terraformを利用してリソースをデプロイし、インストールを実行する手順をまとめたものです。
- システム要件は [こちらのページ](https://docs.uipath.com/ja/automation-suite/automation-suite/2024.10/installation-guide-eks-aks/prerequisites-at-a-glance) をご参照ください。かなりハイスペックなマシンが必要となりますので、AWSの利用料金にはご注意ください。
- 利用するUiPath製品によって要件のマシンスペックが異なります。今回はGPUを利用する **Document Understanding** のインストール手順についても説明しますが、利用されない場合にはその手順はスキップしてください。

## Automation Suiteとは？
- UiPathの各製品サービスを一まとめにしてユーザーが所有する環境に展開できる**全部入りの製品**です。
- v2024.10では [次の製品サービス](https://docs.uipath.com/ja/automation-suite/automation-suite/2024.10/installation-guide-eks-aks/automation-suite-products) がサポートされています。
	- Action Center
	- AI Center
	- Apps
	- Automation Hub
	- Automation Ops
	- Data Service
	- Document Understanding
	- Insights
    - Integration Service
	- Orchestrator
    - Automation Suite Robots
	- Process Mining
    - Studio Web
	- Task Mining
	- Test Manager
- 全部入りという意味ではSaaS型のAutomation Cloudも利用可能ですが、次のような利用上の課題が考えられます。
    - 企業のセキュリティポリシーとして個人情報を扱う業務などではSaaS利用に制約があり、Automation Cloudがセンシティブな業務の自動化には適さない場合がある。
	- インターネット経由でのアクセスのみ許可されており、専用線やVPNのみに接続を限定することはできません。
		- [ソースIPの制限](https://docs.uipath.com/ja/automation-cloud/automation-cloud/latest/admin-guide/restricting-access-by-ip)を設けることはできますが、インターネット経由でのアクセスという点では変わりはありません。
	- Automation CloudはUiPath社が管理するAzure基盤にマルチテナントで展開されております。[専有型のオプションも発表されています](https://docs.uipath.com/ja/automation-cloud-dedicated/automation-cloud-dedicated/latest/release-notes/april-2025#introducing-automation-cloudtm-dedicated)が、利用条件があるため詳細はUiPath社までお問い合わせください。
- これらの課題を解決すべく、いわば「Automation Cloudのオンプレミス版」とも呼べるのがAutomation Suiteになります。UiPath社は2022年9月より、[Business Automation Platform](https://www.uipath.com/ja/newsroom/uipath-previews-new-business-automation-platform-features-at-forward-5) という戦略を打ち出しており、自動化領域の発見から開発、管理・運用、効果計測に至るまで一気通貫した製品群をプラットフォームとして提供しています。つまり **Business Automation Platformをオンプレミスで実現する製品がAutomation Suite** という位置づけになると筆者は捉えています。
    - ただしAutopilotなどの生成AI関連のサービスは現時点ではAutomation Suiteではサポートされていません。詳細は [Automation Cloudとの比較表](https://docs.uipath.com/ja/overview/other/latest/overview/product-availability#product-availability-across-delivery-models) をご覧ください。

# Automation Suiteの構成

## Amazon EKS環境における構成図
- 構成図は次の通りです。(※ 参照: [デプロイのシナリオ](https://docs.uipath.com/ja/automation-suite/automation-suite/2024.10/installation-guide-eks-aks/deployment-scenarios))
    ![AS_EKS構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178312/be01c5fe-87a8-48ee-b2ce-f25e9f534398.png)

- この構成において利用するAWSサービスと用途は次の通りです。

|AWSサービス|用途|
|---|---|
|**EC2 (Windows)**|踏み台サーバー。管理者がRDPで接続|
|**EC2 (Linux)**|作業用Linuxマシン。管理者が踏み台サーバーからSSHで接続<br>`kubectl` コマンドなどを実行してEKSクラスターを操作|
|**EKS**|Automation SuiteをインストールするKubernetesクラスター<br>管理プレーンとワーカーノード(EC2)から構成|
|**RDS for SQL Server**|Automation Suiteの各製品サービスが使用するデータベース|
|**ElastiCahe for Redis**|ワーカーノードから読み書きされ、キャッシュを保持するためのインメモリデータベース|
|**EFS**|Automation Suiteのバックアップファイルを格納するためのファイルサーバー|
|**S3**|Automation Suiteの各製品サービスのアプリケーションデータを格納するストレージ。オブジェクトストアと呼ばれる|
|**Route 53**|Automation Suiteによって使用されるDNSレコードを作成<br>※ 今回は検証用に `lab.test` というローカルドメインを使用|
- 各マシンとワーカーノードからはアウトバウンドへのインターネットアクセスは可能とします。
- ワーカーノードからRDS for SQL Server、ElastiCahe for RedisおよびS3へのアクセスにはそれぞれプライベート接続を使用します。

## サイジング
- EKSワーカーノードは利用するUiPath製品によってインスタンスタイプとノード数を選定します。詳細は [要件算出ツール](https://docs.uipath.com/ja/automation-suite/automation-suite/2024.10/installation-guide-eks-aks/capacity-planning) で試算します。
- 今回はUiPath OrchestratorとDocument Understandingを利用する想定で、次のノード構成で進めてゆきます。
    - `c7a.8xlarge` x3台
    - `g4dn.xlarge` x5台

# Automation Suite 環境構築手順

## AWSリソース作成
- 上の構成図に記載したAWSリソースを手作業で作成するには少々骨が折れるため、本記事ではTerraformにて展開します。Terraformの使い方に慣れていない方は[こちらの記事](https://qiita.com/takaki-imura/items/1d1a2132100300d1010c)を参照してTerraformの実行環境をセットアップしてください。
	- AWSリソースをTerraformにて作成するためには [AWS CLI](https://aws.amazon.com/jp/cli/) をインストールします。
	- [aws configureコマンド](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-quickstart.html)でAWS CLIの設定を行います。
	- Terraformは作成するリソースの単位、変数およびアウトプットなどによってファイルを分割した方が良いのですが、今回は分かりやすさを優先するため **main.tf** という一つのファイルで保存して実行します。各local変数は環境に応じて値を変更します。

|変数名|用途|
|---|---|
|**res_prefix**|各AWSリソースのプレフィックス名|
|**region**|リージョン|
|**availability_zones**|アベイラビリティゾーン|
|**tags**|リソースグループに付けるタグ (省略可)|
|**vpc_address**|VPCアドレス範囲|
|**db_instance_type**|RDS for SQL Serverのインスタンスタイプ|
|**sql_username**|RDS for SQL Serverの管理者ユーザー名|
|**sql_password**|RDS for SQL Serverの管理者パスワード|
|**redis_password**|ElastiCahe for Redisの接続パスワード|
|**as_fqdn**|Automation SuiteのFQDN|
|**enable_public_access**|Automation Suiteへのパブリックアクセス許可<br>閉域網では `false` に設定|
|**cpu_instance_type**|EKSワーカーノード(GPU無し)のインスタンスタイプ<br>[要件算出ツール](https://docs.uipath.com/ja/automation-suite/automation-suite/2024.10/installation-guide-eks-aks/capacity-planning) により決定|
|**number_of_cpu_nodes**|EKSワーカーノード(GPU無し)のインスタンス数|
|**gpu_instance_type**|EKSワーカーノード(GPU有り)のインスタンスタイプ<br>Document Understandingなど利用時|
|**number_of_gpu_nodes**|EKSワーカーノード(GPU有り)のインスタンス数<br>利用しない場合は 0 に設定|
|**kubernetes_version**|EKSクラスターのKubernetesバージョン|
|**my_ip**|踏み台サーバーにRDPアクセスを許可するIPアドレス<br>作業マシンが[インターネット接続しているグローバルIPアドレス確認](https://www.cman.jp/network/support/go_access.cgi)して指定します|

<details><summary>main.tf (クリックして展開)</summary>

```Terraform:
# Local variables (change them according to your environment)
locals {
  res_prefix         = "hidecha-eks"
  region             = "ap-northeast-1"
  availability_zones = ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
  tags = {
    Owner   = "hidecha"
    Project = "Qiita"
  }
  vpc_address          = "10.1.0.0/16"
  db_instance_type     = "db.m6i.2xlarge"
  sql_username         = "sql_admin"
  sql_password         = "SuperSecretPassword"
  redis_password       = "1234567890123456"
  as_fqdn              = "as.lab.test"
  enable_public_access = false
  cpu_instance_type    = "c7a.8xlarge"
  number_of_cpu_nodes  = 3
  gpu_instance_type    = "g4dn.xlarge"
  number_of_gpu_nodes  = 5
  kubernetes_version   = "1.31"
  my_ip                = "x.x.x.x"
}

# Provider
provider "aws" {
  region = local.region
  default_tags {
    tags = local.tags
  }
}

# Virtual Network
## VPC
resource "aws_vpc" "vpc" {
  cidr_block           = local.vpc_address
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "${local.res_prefix}-vpc"
  }
}

## Public Subnets
resource "aws_subnet" "subnet_public" {
  count = length(local.availability_zones)

  vpc_id                  = aws_vpc.vpc.id
  availability_zone       = local.availability_zones[count.index]
  cidr_block              = cidrsubnet(aws_vpc.vpc.cidr_block, 8, count.index + 1)
  map_public_ip_on_launch = true

  tags = {
    Name = "${local.res_prefix}-subnet-public${format("%02d", count.index + 1)}"
  }
}

## Private Subnets
resource "aws_subnet" "subnet_private" {
  count = length(local.availability_zones)

  vpc_id                  = aws_vpc.vpc.id
  availability_zone       = local.availability_zones[count.index]
  cidr_block              = cidrsubnet(aws_vpc.vpc.cidr_block, 8, count.index + 11)
  map_public_ip_on_launch = false

  tags = {
    Name = "${local.res_prefix}-subnet-private${format("%02d", count.index + 1)}"
  }
}

locals {
  public_subnet_ids  = aws_subnet.subnet_public[*].id
  private_subnet_ids = aws_subnet.subnet_private[*].id
}

## Internet Gateway
resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.vpc.id

  tags = {
    Name = "${local.res_prefix}-igw"
  }
}

## Elastic IPs for NAT Gateways
resource "aws_eip" "eip_ngw" {
  count  = length(local.availability_zones)
  domain = "vpc"

  tags = {
    Name = "${local.res_prefix}-eip-ngw${format("%02d", count.index + 1)}"
  }
}

## NAT Gateways
resource "aws_nat_gateway" "ngw" {
  count = length(local.availability_zones)

  connectivity_type = "public"
  subnet_id         = aws_subnet.subnet_public[count.index].id
  allocation_id     = aws_eip.eip_ngw[count.index].id

  tags = {
    Name = "${local.res_prefix}-ngw${format("%02d", count.index + 1)}"
  }

  depends_on = [aws_internet_gateway.igw]
}

## Route table for Public Subnet
resource "aws_route_table" "rt_public" {
  vpc_id = aws_vpc.vpc.id

  tags = {
    Name = "${local.res_prefix}-rt-public"
  }
}

resource "aws_route" "public_internet_gateway" {
  route_table_id         = aws_route_table.rt_public.id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.igw.id
}

## Route Tables for Private Subnets
resource "aws_route_table" "rt_private" {
  count = length(local.availability_zones)

  vpc_id = aws_vpc.vpc.id

  tags = {
    Name = "${local.res_prefix}-rt-private${format("%02d", count.index + 1)}"
  }
}

resource "aws_route" "private_nat_gateway" {
  count = length(local.availability_zones)

  route_table_id         = aws_route_table.rt_private[count.index].id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.ngw[count.index].id
}

## Route Table to Public Subnets association
resource "aws_route_table_association" "rt_public_assoc" {
  count = length(local.availability_zones)

  subnet_id      = aws_subnet.subnet_public[count.index].id
  route_table_id = aws_route_table.rt_public.id
}

## Route Tables to Private Subnets association
resource "aws_route_table_association" "rt_private_assoc" {
  count = length(local.availability_zones)

  subnet_id      = aws_subnet.subnet_private[count.index].id
  route_table_id = aws_route_table.rt_private[count.index].id
}

# Security Groups
## Security Group for Bastion
resource "aws_security_group" "sg_bastion" {
  name   = "${local.res_prefix}-sg-bastion"
  vpc_id = aws_vpc.vpc.id

  ingress {
    from_port   = 3389
    to_port     = 3389
    protocol    = "tcp"
    cidr_blocks = ["${local.my_ip}/32"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${local.res_prefix}-sg-bastion"
  }
}

## Security Group for VPC
resource "aws_security_group" "sg_internal" {
  name   = "${local.res_prefix}-sg-internal"
  vpc_id = aws_vpc.vpc.id

  ingress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = [local.vpc_address]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${local.res_prefix}-sg-internal"
  }
}

# Key Pair
## Generate a secure private key and encodes it as PEM
resource "tls_private_key" "key_pair" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

## Create Key Pair
resource "aws_key_pair" "key_pair" {
  key_name   = "${local.res_prefix}-key-pair"
  public_key = tls_private_key.key_pair.public_key_openssh
}

## Save file
resource "local_sensitive_file" "ssh_key" {
  filename        = "${aws_key_pair.key_pair.key_name}.pem"
  content         = tls_private_key.key_pair.private_key_pem
  file_permission = "0600"
}

# EC2 Instance for Bastion
## Get latest Windows Server AMI
data "aws_ami" "windows_ami" {
  most_recent = true
  owners      = ["amazon"]
  filter {
    name   = "name"
    values = ["Windows_Server-2022-Japanese-Full-Base-*"]
  }
  filter {
    name   = "state"
    values = ["available"]
  }
}

## Create Windows EC2 Instance for Bastion
resource "aws_instance" "vm_basion" {
  ami                         = data.aws_ami.windows_ami.id
  instance_type               = "t3.medium"
  subnet_id                   = aws_subnet.subnet_public[0].id
  vpc_security_group_ids      = [aws_security_group.sg_bastion.id]
  source_dest_check           = false
  key_name                    = aws_key_pair.key_pair.key_name
  associate_public_ip_address = true

  root_block_device {
    volume_size           = 64
    volume_type           = "gp3"
    delete_on_termination = true
    encrypted             = true
  }

  tags = {
    Name = "${local.res_prefix}-bastion"
  }
}

## Elastic IP for Bastion
resource "aws_eip" "eip_vm" {
  domain = "vpc"

  tags = {
    Name = "${local.res_prefix}-eip-bastion"
  }
}

### Elastic IP to Bastion association
resource "aws_eip_association" "eip_vm_assoc" {
  instance_id   = aws_instance.vm_basion.id
  allocation_id = aws_eip.eip_vm.id
}

# EC2 Instance for Client
## Get latest Amazon Linux 2023 AMI
data "aws_ami" "al2023" {
  most_recent = true
  owners      = ["amazon"]
  filter {
    name   = "name"
    values = ["al2023-ami-2023.*-kernel-*-x86_64"]
  }
  filter {
    name   = "state"
    values = ["available"]
  }
}

## Create EC2 Instance for Client
resource "aws_instance" "vm_client" {
  ami                         = data.aws_ami.al2023.id
  instance_type               = "t3.medium"
  subnet_id                   = aws_subnet.subnet_private[0].id
  vpc_security_group_ids      = [aws_security_group.sg_internal.id]
  source_dest_check           = false
  key_name                    = aws_key_pair.key_pair.key_name
  associate_public_ip_address = false

  # root disk
  root_block_device {
    volume_size           = 64
    volume_type           = "gp3"
    delete_on_termination = true
    encrypted             = true
  }

  tags = {
    Name = "${local.res_prefix}-client"
  }
}

# Create IAM Role for EKS
## IAM Role for EKS Control Plane
resource "aws_iam_role" "iam_role_controlplane" {
  name = "${local.res_prefix}-controlplane-role"
  assume_role_policy = jsonencode({
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = {
        Service = "eks.amazonaws.com"
      }
    }]
    Version = "2012-10-17"
  })
}

resource "aws_iam_role_policy_attachment" "iam_role_controlplane_policy" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
  role       = aws_iam_role.iam_role_controlplane.name
}

## IAM Role for EKS Worker Node
resource "aws_iam_role" "iam_role_workernode" {
  name = "${local.res_prefix}-workernode-role"
  assume_role_policy = jsonencode({
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = {
        Service = "ec2.amazonaws.com"
      }
    }]
    Version = "2012-10-17"
  })
}

resource "aws_iam_role_policy_attachment" "iam_role_workernode_policy" {
  for_each = toset([
    "AmazonEKSWorkerNodePolicy",
    "AmazonEKS_CNI_Policy",
    "AmazonEC2ContainerRegistryReadOnly",
    "AmazonS3FullAccess"
  ])

  policy_arn = "arn:aws:iam::aws:policy/${each.key}"
  role       = aws_iam_role.iam_role_workernode.name
}

resource "aws_iam_role_policy" "iam_policy_workernode_ebs" {
  name = "EBS_CSI_Driver"
  role = aws_iam_role.iam_role_workernode.name
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "ec2:AttachVolume",
          "ec2:CreateSnapshot",
          "ec2:CreateTags",
          "ec2:CreateVolume",
          "ec2:DeleteSnapshot",
          "ec2:DeleteTags",
          "ec2:DeleteVolume",
          "ec2:DescribeInstances",
          "ec2:DescribeSnapshots",
          "ec2:DescribeTags",
          "ec2:DescribeVolumes",
          "ec2:DetachVolume"
        ]
        Resource = "*"
      }
    ]
  })
}

resource "aws_iam_role_policy" "iam_policy_workernode_efs" {
  name = "EFS_CSI_Driver"
  role = aws_iam_role.iam_role_workernode.name
  policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Effect" : "Allow",
        "Action" : [
          "elasticfilesystem:DescribeAccessPoints",
          "elasticfilesystem:DescribeFileSystems",
          "elasticfilesystem:DescribeMountTargets",
          "ec2:DescribeAvailabilityZones"
        ],
        "Resource" : "*"
      },
      {
        "Effect" : "Allow",
        "Action" : [
          "elasticfilesystem:CreateAccessPoint"
        ],
        "Resource" : "*",
        "Condition" : {
          "StringLike" : {
            "aws:RequestTag/efs.csi.aws.com/cluster" : "true"
          }
        }
      },
      {
        "Effect" : "Allow",
        "Action" : "elasticfilesystem:DeleteAccessPoint",
        "Resource" : "*",
        "Condition" : {
          "StringEquals" : {
            "aws:ResourceTag/efs.csi.aws.com/cluster" : "true"
          }
        }
      }
    ]
  })
}

resource "aws_iam_role_policy" "iam_policy_workernode_elb" {
  name = "ELB_Permissions"
  role = aws_iam_role.iam_role_workernode.name
  policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Sid" : "VisualEditor0",
        "Effect" : "Allow",
        "Action" : [
          "elasticloadbalancing:ModifyListener",
          "elasticloadbalancing:SetLoadBalancerPoliciesForBackendServer",
          "elasticloadbalancing:DetachLoadBalancerFromSubnets",
          "elasticloadbalancing:AttachLoadBalancerToSubnets",
          "elasticloadbalancing:RegisterTargets",
          "elasticloadbalancing:ConfigureHealthCheck",
          "elasticloadbalancing:CreateTargetGroup",
          "elasticloadbalancing:*",
          "elasticloadbalancing:DeleteTargetGroup",
          "elasticloadbalancing:CreateLoadBalancerListeners",
          "elasticloadbalancing:CreateListener",
          "elasticloadbalancing:DescribeListeners",
          "elasticloadbalancing:SetLoadBalancerPoliciesOfListener",
          "elasticloadbalancing:DescribeTargetHealth",
          "elasticloadbalancing:DescribeLoadBalancerPolicies",
          "elasticloadbalancing:DescribeTargetGroups",
          "elasticloadbalancing:ApplySecurityGroupsToLoadBalancer",
          "elasticloadbalancing:DeleteLoadBalancerListeners",
          "elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
          "elasticloadbalancing:RegisterInstancesWithLoadBalancer",
          "elasticloadbalancing:CreateLoadBalancerPolicy",
          "elasticloadbalancing:ModifyTargetGroup",
          "elasticloadbalancing:DeleteListener"
        ],
        "Resource" : "*"
      }
    ]
  })
}

# Create EKS Cluster
resource "aws_eks_cluster" "eks_cluster" {
  name     = "${local.res_prefix}-cluster"
  role_arn = aws_iam_role.iam_role_controlplane.arn
  version  = local.kubernetes_version

  vpc_config {
    subnet_ids         = local.enable_public_access ? local.public_subnet_ids : local.private_subnet_ids
    security_group_ids = [aws_security_group.sg_internal.id]
  }

  depends_on = [
    aws_iam_role_policy_attachment.iam_role_controlplane_policy
  ]
}

## Add-On
resource "aws_eks_addon" "eks_addons" {
  for_each = toset(["vpc-cni", "coredns", "kube-proxy", "aws-ebs-csi-driver", "aws-efs-csi-driver"])

  cluster_name = aws_eks_cluster.eks_cluster.name
  addon_name   = each.key

  depends_on = [
    aws_eks_cluster.eks_cluster,
    aws_eks_node_group.eks_nodegroup_cpu
  ]
}

## Launch Template for EKS Node Group
resource "aws_launch_template" "eks_nodegroup_template" {
  name = "${local.res_prefix}-nodegroup-template"

  block_device_mappings {
    device_name = "/dev/xvda"

    ebs {
      volume_size           = 256
      volume_type           = "gp3"
      delete_on_termination = true
    }
  }

  metadata_options {
    http_endpoint               = "enabled"
    http_put_response_hop_limit = 2
    http_tokens                 = "required"
  }

  tag_specifications {
    resource_type = "instance"
    tags          = local.tags
  }
}

## Create EKS Node Group (CPU)
resource "aws_eks_node_group" "eks_nodegroup_cpu" {
  count = local.number_of_cpu_nodes > 0 ? 1 : 0

  cluster_name    = aws_eks_cluster.eks_cluster.name
  node_group_name = "${local.res_prefix}-nodegroup-cpu"
  ami_type        = "AL2023_x86_64_STANDARD"
  node_role_arn   = aws_iam_role.iam_role_workernode.arn
  subnet_ids      = local.enable_public_access ? local.public_subnet_ids : local.private_subnet_ids
  instance_types  = [local.cpu_instance_type]

  scaling_config {
    desired_size = local.number_of_cpu_nodes
    min_size     = local.number_of_cpu_nodes
    max_size     = local.number_of_cpu_nodes > 0 ? local.number_of_cpu_nodes : 1
  }

  update_config {
    max_unavailable = 1
  }

  launch_template {
    id      = aws_launch_template.eks_nodegroup_template.id
    version = aws_launch_template.eks_nodegroup_template.latest_version
  }

  depends_on = [
    aws_iam_role_policy_attachment.iam_role_workernode_policy
  ]
}

## Create EKS Node Group (GPU)
resource "aws_eks_node_group" "eks_nodegroup_gpu" {
  count = local.number_of_gpu_nodes > 0 ? 1 : 0

  cluster_name    = aws_eks_cluster.eks_cluster.name
  node_group_name = "${local.res_prefix}-nodegroup-gpu"
  ami_type        = "AL2023_x86_64_NVIDIA"
  node_role_arn   = aws_iam_role.iam_role_workernode.arn
  subnet_ids      = local.enable_public_access ? local.public_subnet_ids : local.private_subnet_ids
  instance_types  = [local.gpu_instance_type]

  scaling_config {
    desired_size = local.number_of_gpu_nodes
    min_size     = local.number_of_gpu_nodes
    max_size     = local.number_of_gpu_nodes > 0 ? local.number_of_gpu_nodes : 1
  }

  taint {
    key    = "nvidia.com/gpu"
    value  = "present"
    effect = "NO_SCHEDULE"
  }

  labels = {
    "accelerator" = "nvidia-gpu"
  }

  update_config {
    max_unavailable = 1
  }

  launch_template {
    id      = aws_launch_template.eks_nodegroup_template.id
    version = aws_launch_template.eks_nodegroup_template.latest_version
  }

  depends_on = [
    aws_iam_role_policy_attachment.iam_role_workernode_policy
  ]
}

# DNS Zone
resource "aws_route53_zone" "dns_zone" {
  name = local.as_fqdn
  vpc {
    vpc_id = aws_vpc.vpc.id
  }

  lifecycle {
    ignore_changes = [vpc]
  }
}

resource "aws_route53_record" "dns_cname_record" {
  for_each = toset(["alm", "monitoring", "objectstore", "registry", "insights", "apps"])

  zone_id = aws_route53_zone.dns_zone.zone_id
  name    = "${each.key}.${local.as_fqdn}"
  type    = "CNAME"
  ttl     = 300
  records = [local.as_fqdn]
}

# EFS File system
resource "aws_efs_file_system" "efs_file" {
  creation_token = "${local.res_prefix}-efs"
  encrypted      = true

  tags = {
    Name = "${local.res_prefix}-efs"
  }
}

# EFS Mount target
resource "aws_efs_mount_target" "efs_mount" {
  count = length(local.availability_zones)

  file_system_id  = aws_efs_file_system.efs_file.id
  subnet_id       = local.enable_public_access ? local.public_subnet_ids[count.index] : local.private_subnet_ids[count.index]
  security_groups = [aws_security_group.sg_internal.id]
}

# RDS instance for SQL Server 
## DB Subnet
resource "aws_db_subnet_group" "db_subnet" {
  name       = "${local.res_prefix}-db-subnet-gp"
  subnet_ids = [aws_subnet.subnet_private[0].id, aws_subnet.subnet_private[1].id]
}

## DB Parameter Group
resource "aws_db_parameter_group" "sqlserver_pg" {
  name   = "${local.res_prefix}-sqlserver-pg"
  family = "sqlserver-se-15.0"
}

## DB Option Group
resource "aws_db_option_group" "sqlserver_opg" {
  name                 = "${local.res_prefix}-sqlserver-opg"
  engine_name          = "sqlserver-se"
  major_engine_version = "15.00"
}

## DB Instance
resource "aws_db_instance" "sqlserver_instance" {
  identifier     = "${local.res_prefix}-database"
  instance_class = local.db_instance_type
  engine         = "sqlserver-se"
  engine_version = "15.00.4236.7.v1"
  license_model  = "license-included"
  multi_az       = false
  username       = local.sql_username
  password       = local.sql_password

  # storage
  storage_type          = "gp3"
  allocated_storage     = 256
  max_allocated_storage = 1000
  storage_encrypted     = true

  # network
  db_subnet_group_name   = aws_db_subnet_group.db_subnet.name
  vpc_security_group_ids = [aws_security_group.sg_internal.id]
  port                   = 1433

  # backup snapshot
  backup_retention_period  = 7
  copy_tags_to_snapshot    = true
  delete_automated_backups = true
  deletion_protection      = false
  skip_final_snapshot      = true

  # window time
  backup_window      = "01:00-01:30"
  maintenance_window = "Mon:02:00-Mon:03:00"

  # options
  parameter_group_name       = aws_db_parameter_group.sqlserver_pg.name
  option_group_name          = aws_db_option_group.sqlserver_opg.name
  character_set_name         = "SQL_Latin1_General_CP1_CI_AS"
  timezone                   = "Tokyo Standard Time"
  auto_minor_version_upgrade = false
}

# Create ElastiCache
## ElastiCache subnet
resource "aws_elasticache_subnet_group" "redis_subnet" {
  name       = "${local.res_prefix}-redis-subnet-gp"
  subnet_ids = local.private_subnet_ids
}

## ElastiCache Replication Group
resource "aws_elasticache_replication_group" "redis_repgroup" {
  automatic_failover_enabled  = false
  preferred_cache_cluster_azs = local.availability_zones
  replication_group_id        = "${local.res_prefix}-redis-repgroup"
  description                 = "Redis Replication Group"
  node_type                   = "cache.t3.medium"
  num_cache_clusters          = length(local.availability_zones)
  engine                      = "redis"
  engine_version              = "7.1"
  parameter_group_name        = "default.redis7"
  port                        = 6380
  auth_token                  = local.redis_password
  transit_encryption_enabled  = true
  subnet_group_name           = aws_elasticache_subnet_group.redis_subnet.name
  security_group_ids          = [aws_security_group.sg_internal.id]
}

# Outputs
output "bastion_public_ip_address" {
  value = aws_eip.eip_vm.public_ip
}

output "client_private_ip_address" {
  value = aws_instance.vm_client.private_ip
}

output "sqlserver_hostname" {
  value = aws_db_instance.sqlserver_instance.address
}

output "redis_endpoint" {
  value = aws_elasticache_replication_group.redis_repgroup.primary_endpoint_address
}

output "private_subnet_ip_ids" {
  value = join(",", local.private_subnet_ids)
}
```
</details>

:::note warn
上記TerraformはElastiCacheとRDSをシングルAZでデプロイしています。マルチAZによる冗長化が必要な場合など、ご要件や環境に応じて変更を行ってください。
:::

- 準備が整いましたらmain.tfが配置されたディレクトリに移動し、次のコマンドを一行ずつ実行し、Automation Suiteに必要なAWSリソースを展開します。
    ```bash
	terraform init
    ```
    ```bash
	terraform plan -out main.tfplan
    ```
    ```bash
	terraform apply main.tfplan
    ```
    - Terraformによるリソース作成が成功することを確認します。
    - AWS管理コンソールにアクセスし、各リソースが作成されていることを確認します。
- Terraformを用いずに手動でAWSリソースを作成することもできます。詳細な手順は [前提条件の一覧](https://docs.uipath.com/ja/automation-suite/automation-suite/2024.10/installation-guide-eks-aks/prerequisites-at-a-glance) をご参照ください。

## 前提条件のコンポーネントインストールなど

Automation Suiteインストールに必要な前提条件のコンポーネントのインストールや設定などを行います。

### SSH接続
まず踏み台サーバーにRDPログインし、そこから作業用LinuxクライアントマシンにSSHで接続します。

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178312/aac4840c-ef45-48c6-9dd1-b1bbf40093f8.png)

以降Linuxマシンにて順次コマンド実行します。

### 環境変数定義
- AWSマネジメントコンソールのアクセスキーに表示されるAWS環境変数をコピーし、SSHクライアントにペーストします。[aws configureコマンド](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-quickstart.html) でアクセスする方法は [後述します]()。

    ![21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178312/91f7c3ca-4912-45ed-821f-1ab9b25de1a6.png)

- 変数定義 (環境に応じて変更)
    ```bash
    AS_VER=2024.10.2                    # インストールするAutomation Suiteバージョン
    REGION=ap-northeast-1               # 東京リージョン    
    PREFIX=hidecha-eks                  # Terraformのres_prefix
    CLUSTER_NAME=${PREFIX}-cluster      # EKS
    ROLE_NAME=${PREFIX}-workernode-role # ワーカーノードIAMロール
    EFS_NAME=${PREFIX}-efs              # EFS
    ```

### hop limit確認
- EKSワーカーノードの **hop limit** が2であることを確認します。これはAmazon Linux 2023のhop limitの値が既定で1になっており、Pod上からインスタンスプロファイルを使用するためのAPIにアクセスできない問題が発生するため、Terraformの起動テンプレートにて既にhop limitを2に変更しています。詳細は [こちらの記事](https://zenn.dev/tsuzucle_tech/articles/0437af82c0a9c6) をご参照ください。
    ```bash
    aws ec2 describe-instances \
      --region ${REGION} \
      --filters "Name=tag:eks:cluster-name,Values=${CLUSTER_NAME}" "Name=instance-state-name,Values=running" \
      --query "Reservations[].Instances[].{InstanceId:InstanceId,HopLimit:MetadataOptions.HttpPutResponseHopLimit}" \
      --output table
    ```

### kubectl
- kubectlインストール
    ```bash
    curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
    sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
    ```
- kubeconfigファイルを作成
    ```bash
    aws eks update-kubeconfig --name ${CLUSTER_NAME} --region ${REGION}
    ```
- Kubernetesクラスタのすべてのリソース確認
    ```bash
    kubectl get all -A
    ```

### NVIDIAアドイン
- GPUノードを利用している場合、[NVIDIAアドイン](https://github.com/NVIDIA/k8s-device-plugin) をインストール
    ```bash
    kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.17.1/deployments/static/nvidia-device-plugin.yml
    ```

### EBSストレージクラス作成
- [EBSストレージクラス作成](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/ebs-csi.html)
    ```bash
    sudo dnf install -y git
    git clone https://github.com/kubernetes-sigs/aws-ebs-csi-driver.git
    echo "parameters:
      type: gp3" >> aws-ebs-csi-driver/examples/kubernetes/dynamic-provisioning/manifests/storageclass.yaml
    kubectl apply -f aws-ebs-csi-driver/examples/kubernetes/dynamic-provisioning/manifests/storageclass.yaml
    kubectl describe storageclass ebs-sc
    ```

### EFSストレージクラス作成
- [EFSストレージクラス作成](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/efs-csi.html)
- 信頼関係ポリシー更新
    ```bash
    oidc_id=$(aws eks describe-cluster --name ${CLUSTER_NAME} --query "cluster.identity.oidc.issuer" --output text | cut -d '/' -f 5)
    cat <<EOF > trust-policy.json
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": {
            "Federated": "arn:aws:iam::225248685317:oidc-provider/oidc.eks.region-code.amazonaws.com/id/${oidc_id}"
          },
          "Action": "sts:AssumeRoleWithWebIdentity",
          "Condition": {
            "StringLike": {
              "oidc.eks.region-code.amazonaws.com/id/${oidc_id}:sub": "system:serviceaccount:kube-system:efs-csi-*",
              "oidc.eks.region-code.amazonaws.com/id/${oidc_id}:aud": "sts.amazonaws.com"
            }
          }
        }
      ]
    }
    EOF
    aws iam update-assume-role-policy --role-name ${ROLE_NAME} --policy-document file://trust-policy.json
    ```
- サービスアカウント作成
    ```bash
    cat <<EOF > efs-service-account.yaml
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      labels:
        app.kubernetes.io/name: aws-efs-csi-driver
      name: efs-csi-controller-sa
      namespace: kube-system
      annotations:
        eks.amazonaws.com/role-arn: arn:aws:iam:role/${ROLE_NAME}
    EOF
    kubectl apply -f efs-service-account.yaml
    ```
- eksctlインストール
    ```bash
    ARCH=amd64
    PLATFORM=$(uname -s)_$ARCH
    curl -sLO "https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_$PLATFORM.tar.gz"
    curl -sL "https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_checksums.txt" | grep $PLATFORM | sha256sum --check
    tar -xzf eksctl_$PLATFORM.tar.gz -C /tmp && rm eksctl_$PLATFORM.tar.gz
    sudo mv /tmp/eksctl /usr/local/bin
    ```
- OIDCプロバイダー信頼
    ```bash
    eksctl utils associate-iam-oidc-provider --region=${REGION} --cluster=${CLUSTER_NAME} --approve
    ```
- EFSストレージクラスのサンプルアプリ作成
    ```bash
    file_system_id=$(aws efs describe-file-systems --query "FileSystems[?Name=='${EFS_NAME}'].FileSystemId" --output text)
    git clone https://github.com/kubernetes-sigs/aws-efs-csi-driver.git
    cat aws-efs-csi-driver/examples/kubernetes/dynamic_provisioning/specs/storageclass.yaml | sed "s/fileSystemId: fs-92107410/fileSystemId: ${file_system_id}/" > efs-storageclass.yaml
    kubectl apply -f efs-storageclass.yaml
    cat aws-efs-csi-driver/examples/kubernetes/dynamic_provisioning/specs/pod.yaml | sed "s/image: centos/image: almalinux/" > efs-pod.yaml
    kubectl apply -f efs-pod.yaml
    ```
- 動作確認
    ```bash
    kubectl get pvc
    kubectl get pod/efs-app
    ```

### Metrics Server
- [Metrics Server](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/metrics-server.html)インストール
    ```bash
    kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
    ```

### EKSセキュリティグループ変更
- EKSセキュリティグループを変更し、インバウンドHTTPSを追加
    ```bash
    sg_id=$(aws eks describe-cluster --name ${CLUSTER_NAME} --query "cluster.resourcesVpcConfig.clusterSecurityGroupId" --output text)
    aws ec2 authorize-security-group-ingress \
      --group-id ${sg_id} \
      --protocol tcp \
      --port 443 \
      --cidr 0.0.0.0/0
    ```

### uipathctl
- [uipathctl](https://docs.uipath.com/ja/automation-suite/automation-suite/2024.10/reference-guide/about-uipathctl) インストール
    ```bash
    curl https://download.uipath.com/uipathctl/${AS_VER}/uipathctl-${AS_VER}-linux-amd64.tar.gz -o uipathctl.tar.gz
    tar xzvf uipathctl.tar.gz
    chmod +x uipathctl
    sudo mv ./uipathctl /usr/local/bin
    uipathctl version
    ```

### versions.json
- versions.jsonダウンロード
    ```bash
    curl https://download.uipath.com/automation-suite/${AS_VER}/versions.json -o versions.json
    ```

## input.json作成
- Automation Suiteの環境設定およびインストールされるUiPath製品の定義として `input.json` のファイルを作成します。[パラメーター定義](https://docs.uipath.com/ja/automation-suite/automation-suite/2024.10/installation-guide-eks-aks/configuring-inputjson) を参照して手動で作成することもできますが、設定項目が非常に多いため [ウィザード](https://docs.uipath.com/ja/automation-suite/automation-suite/2024.10/installation-guide-eks-aks/generating-the-configuration-file-using-wizard) を利用して主要なパラメーター設定を行い、手作業で詳細設定を行います。

- ウィザードを起動するには次のコマンドを実行します。
    ```bash
    uipathctl config generate --port 8080
    ```
- ブラウザーで `http://<Linux-IP>:8080` にアクセスします。
    ![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178312/4581492f-6e2c-463c-8a87-bad1ff8cc3dc.png)

    - **I accept the license agreement** をオンにして Nextをクリックします。
    1. Platform:
        - **Automation Suite on EKS** を選択して、Nextをクリックします。
        ![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178312/9348fc7f-586d-4b9e-b81f-e7bf7dced5eb.png)
        
    2. Basic Configuration
        - Deployment mode: **Online** を選択
        - Deployment type: **Highly Available** を選択
        - Istio Minimum Protocol Version: **TLSV1_3** を選択
        ![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178312/15a28541-04a4-4f3c-bf2b-0fe2b4db8050.png)
        
        - Automation Suite FQDN: `as.lab.test` などと指定
        - Admin Username: `admin` を指定
        - Admin Password: Automation Suite管理画面ログイン時のパスワードを指定
        - Storage Class: `ebs-sc` を指定
        - Select Services: 利用するUiPath製品を選択します。今回は `Orchestrator`, `AI Center`, `Document Understanding` を選択し、Nextをクリックします。
        ![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178312/105f6dc7-dc1d-4176-81bb-16948ec05bfa.png)
        
    3. Database Configurations
        - SQL Database
            - Create Database: オン
            - FQDN: RDS for SQL Serverのエンドポイントを指定
            - Port: `1433` を指定
            - Username: `sql_admin` などSQL Serverの管理者を指定
            - Password: SQL Server管理者パスワードを指定
            - ※ Dotnet, JDBC, ODBC, SQLAlchemy Connect Stringは自動設定されるため編集不要
            ![06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178312/42c8776d-a573-4b3e-ad19-43a857a1bf0c.png)

        - Redis Cache
            - Endpoint: ElastiCache for Redisのエンドポイントを指定
            - Database Password: ElastiCache for Redisの接続パスワードを指定
            - Database Port: `6380` を指定
            - TLS: オン
        ![07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178312/1ed91ad4-19e5-4277-965d-835842ecabf7.png)

        - Object Store
            - Storage Type: **S3 Bucket Store** を選択
            - Create Buckets: オン
            - Region: `ap-northeast-1` を指定
            - FQDN: `s3.ap-northeast-1.amazonaws.com`
            - Port: `443`
            - Instance Profile: オン
            - Bucket Prefix: 任意の文字列を指定
            - Bucket Suffix: 任意の文字列を指定して、Nextをクリックします。
            - ※ `{Prefix}-uipath-as-{製品名}-{Suffix}` という命名規則でUiPath製品ごとにS3バケットが作成されます。
            ![08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178312/2906a932-106a-4cb5-8e13-2699df125dd9.png)

    4. Registry & Certificate
        - サーバー証明書の設定を行います。サーバー証明書はインストール後に置き換えることもできるため、ここでは何も入力せずにNextをクリックします。
        ![09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178312/3909a296-1214-42c4-9628-218ec71a5540.png)

    5. Networking
        - NLBの設定を行います。接続方式によって設定内容が異なりますが、ここではプライベートサブネットで動的にNLBを作成・利用する場合を想定した設定を行います。
            - "service.beta.kubernetes.io/aws-load-balancer-eip-allocations": 削除
            - "service.beta.kubernetes.io/aws-load-balancer-scheme": `"internal"` に変更
            - "service.beta.kubernetes.io/aws-load-balancer-subnets": 3つのアベイラビリティゾーンのプライベートサブネットのIDをカンマ区切りで追加 (Terraform `private_subnet_ip_ids` 出力値)
            - "service.beta.kubernetes.io/aws-load-balancer-internal": `"true"` を追加
            - "service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags": 必要に応じてNLBに付加するタグを追加し、Nextをクリックします。
            ![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178312/c405b122-4773-4ee9-be14-7a6ec0a2230c.png)

    6. Advanced Configurations
        - Document Understanding: オンを指定し、Nextをクリックします。
        ![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178312/10febc3d-1aea-49c5-89c9-6c4dfad2e966.png)

    7. Product Specific Configuration
        - Modern Projects: オン
    8. Download
        - **Download** をクリックし、input.jsonファイルをローカルにダウンロードします。
    ![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178312/3c6e5c77-c362-4004-a1fb-ecee2f91eabd.png)
- ダウンロードしたinput.jsonをエディターで開き、2点修正します。
    1. `"storage_class": "ebs-sc",` の後に次の行を追加: `"storage_class_single_replica": "efs-sc",`
        ![13a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178312/7748f4d1-771d-4617-b4ff-eb25a0ddb3ff.png)

    2. [Extended Language OCR](https://docs.uipath.com/ja/automation-suite/automation-suite/2024.10/installation-guide-eks-aks/enable-or-disable-extended-languages-ocr) を利用する場合は `"documentunderstanding": {}` セクションに次の行を追加: `"extendedOcr": {"enabled": true, "license": "<base64-license-code>" },`
        ![14a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178312/43e36a54-c026-48fb-a662-0b9752944e5f.png)
        - ※ `<base64-license-code>` 部分はExtended Language OCRの実際のライセンスコードに置き換えます。

- LinuxマシンにてCtrl+Cでウィザードを終了します。
- `vi input.json` を実行し、内容をコピー&ペーストして保存します。

## Automation Suiteインストール
`input.json` が作成できましたら、いよいよAutomation Suiteインストールを実行します。

### データベース / S3バケット作成
```bash
uipathctl prereq create input.json --versions versions.json
```

### ロードバランサー作成
```bash
uipathctl manifest apply input.json --versions versions.json --override=gateway
```
- AWSマネージメントコンソール > EC2 > ロードバランサーにて新しいNLBが作成されていることを確認します。

### DNSレコード作成
- AWSマネージメントコンソール > Route 53にてAutomation Suite FQDN(例: `as.lab.test`)のAレコードをNLBエイリアスに紐づけします。
![16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178312/a9c6807e-7e8d-4254-93c9-68ec96bb6e85.png)

- DNSレコード更新が伝播するまでTTL(300秒)の間、待機します。

### 前提条件チェック
```bash
uipathctl prereq run input.json --versions versions.json
```
- 次のエラーは無視してOKです。
    - `[ECHO_SERVER_ACCESS] Echo server is not expected to be accessible from curl pod`
    - `[DB_ROLES] SQL user does not have db_securityadmin role to DB`
    - ※ ただしProcess Miningをインストールする場合はSQL Server管理者がProcess　Miningのデータベースに対してdb_securityadminロールを付与する必要があります。
- DNSエラーが出る場合はレコード更新が伝播していない可能性があるため、しばらくしてからコマンドを再実行します。

### Automation Suiteインストール実行
```bash
uipathctl manifest apply input.json --versions versions.json
```
- インストール完了までに1時間程度かかります。インストール時のPodの状態はArgoCDで監視することができます。

### ArgoCDアクセス
- インストールの進捗状況はArgoCDで確認できます。
    - SSHクライアントにて別ターミナルを開き、次のコマンドにてArgoCDパスワードを取得します。
        ```bash
        kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
        ```
    - ブラウザーにて `https://alm.{AS-FQDN}` (例: `https://alm.as.lab.test`) を開き、adminユーザー/先ほど取得したパスワードにてログインします。
        ![17.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178312/857c3643-a568-4e5c-8434-031e8730fcea.png)

    - 各アプリケーションのインストール状況が色分けして表示されます。
        - 緑(Synced): インストール済み
        - 青(Progressing): インストール中
        - 赤(Degraded): インストール失敗
        ![18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178312/765fbab9-b542-4a6f-9dc0-d76b90e08090.png)

### サポートバンドル取得方法
- インストール完了時にはすべて緑で表示されますが、2~3時間経過しても青や赤が残る場合にはインストールが失敗している可能性があります。次のコマンドにて [サポートバンドル](https://docs.uipath.com/ja/automation-suite/automation-suite/2024.10/installation-guide-eks-aks/running-the-support-bundle-tool) を取得し、UiPath カスタマーサポートへのお問い合わせの際に添付します。
    ```bash
    uipathctl health bundle input.json --versions versions.json
    ```

## インストール後の作業など

### 管理画面へのログイン
- ブラウザーにて https://{AS-FQDN} (例: `https://as.lab.test`) を開き、Automation Suiteのログイン画面が表示されることを確認します。
    ![19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178312/404e7733-7b6d-4549-a523-4e36ee22753a.png)

    - host組織にログインするには `organization: host, Username: admin, Password: {ウィザードで指定したパスワード}` にてログインします。
    - default組織にログインするには `organization: default, Username: orgadmin, Password: {ウィザードで指定したパスワード}` にてログインします。初回ログイン時にパスワードを変更します。

### サーバー証明書の入れ替え
- Automation Suiteのインストール時に自動作成される自己署名証明書の有効期限は **90日間** です。有効期限が切れる前に別のサーバー証明書を準備し、入れ替えを行う必要があります。詳細な手順は [証明書を管理する](https://docs.uipath.com/ja/automation-suite/automation-suite/2024.10/installation-guide-eks-aks/managing-the-certificates) をご参照ください。

### 変更インストール
- `input.json` などを変更して、Automation Suiteのインストールを再実行するには次のコマンドを実行します。
    ```bash
    uipathctl manifest apply --dry-run input.json --versions versions.json
    ```
    
    ```bash
    uipathctl manifest apply input.json --versions versions.json --skip-helm
    ```

### aws configureによるkubectl実行
- 今回はAWS環境変数を使用してkubectlを実行しましたが、Automation Suite運用時に毎回コピー＆ペーストするのは面倒です。aws configureで指定したIAMユーザーでkubectlを実行するには次の手順を実行します。
- 次のコマンドを実行し、aws-authのConfigMapにIAMユーザーを追加します。
    ```bash
    USERNAME=hidecha-iam-user	        # aws configureで設定するIAMユーザー名
    REGION=ap-northeast-1               # 東京リージョン    
    PREFIX=hidecha-eks                  # Terraformのres_prefix
    CLUSTER_NAME=${PREFIX}-cluster      # EKS
    account_id=$(aws sts get-caller-identity --query Account --output text)
    eksctl create iamidentitymapping \
      --cluster ${CLUSTER_NAME} \
      --region ${REGION} \
      --arn arn:aws:iam::${account_id}:user/${USERNAME} \
      --username ${USERNAME} \
      --group system:masters
    ```
- 新しいSSHセッションを起動し、kubectlが実行できることを確認します。
    ```bash
    kubectl get all -A
    ```

# おわりに
- 今回はAutomation SuiteのインストールをAmazon EKS環境にて実行する手順を説明しました。
- UiPathのすべての製品を自前のの環境で展開できるのがAutomation Suiteの大きなメリットです。ご関心のある方はぜひAutomation Suiteのインストールにチャレンジしていただけると幸いです！