はじめに
- 本記事ではUiPath Automation Suite v2022.10.4をAWS環境にてTerraformを利用してインストールする手順をまとめたものです。
- Azure環境での手順はこちらの記事をご参照ください。
- Automation Suiteはハードウェアおよびソフトウェアの要件に記載の通り、そこそこマシンスペックが必要(c5a.4xlarge以上)なためインストールを試行している間にもそれなりに課金が発生($0.768/時間~)しますのでご注意ください。
Automation Suiteとは?
- UiPathの各製品サービスを一まとめにしてユーザーが所有する環境に展開できる全部入りの製品です。v2022.10では次の製品サービスがサポートされています。
- Orchestrator
- Action Center
- AI Center
- Apps
- Automation Hub
- Automation Ops
- Data Service
- Document Understanding
- Insights
- Process Mining
- Automation Suite Robots
- Task Mining
- Test Manager
- 全部入りという意味ではSaaS型のAutomation Cloudも利用可能ですが、次のような利用上の課題が考えられます。
- インターネット経由でのアクセスのみ許可されており、専用線やVPNのみに接続を限定することはできません。
- ソースIPの制限を設けることはできますが、インターネット経由でのアクセスという点では変わりはありません。
- Automation CloudはUiPath社が管理するAzure基盤にマルチテナントで展開されており、現在のところ専有テナントで展開するオプションはありません。
- インターネット経由でのアクセスのみ許可されており、専用線やVPNのみに接続を限定することはできません。
- これらの課題を解決すべく、いわば「Automation Cloudのオンプレミス版」とも呼べるのがAutomation Suiteになります。UiPath社は2022年9月より、Business Automation Platform という戦略を打ち出しており、自動化領域の発見から開発、管理・運用、効果計測に至るまで一気通貫した製品群をプラットフォームとして提供しています。つまり「Business Automation Platformをオンプレミスで実現する製品がAutomation Suite」という位置づけになると筆者は捉えています。
Automation Suiteの構成
AWS環境での構成図
-
今回は検証環境としてシングル構成にてAutomation Suiteを展開します。構成図は次の通りです。
- EC2 (Bastion): 踏み台サーバー兼作業用Windowsマシン
- EC2 (WebAPサーバー): Automation SuiteをインストールするLinuxマシン (ソフトウェア要件より、現状はRHEL8.xのみサポート)
- RDS for SQL Server: Automation Suiteの各製品サービスが使用するデータベース (複数利用)
- S3: Automation Suiteの各製品サービスのアプリケーションデータを格納するストレージ (「オブジェクトストア」と呼ばれる)
-
Route 53: Automation Suiteによって使用されるDNSレコードを作成 (今回は検証用に
lab.test
というローカルドメインを使用)
-
WebAPサーバーからインターネットアクセスは可能とします。
-
WebAPサーバーからRDS for SQL ServerとS3へのアクセスにはそれぞれプライベート接続を使用します。
Automation Suiteのサイジング
- 利用するUiPath製品サービスの数に比例して、Automation Suiteのマシンスペック要件も高くなります。詳細はハードウェア要件をご確認ください。
- また キャパシティ計算ツール を利用して、利用する製品サービスに応じて必要なマシンスペックを詳細に見積もることができます。
- 今回は利用するUiPath製品サービスはOrchestratorとAutomation Hubとします。先ほどのキャパシティ計算ツールを使用すると次のように必要なAWSリソースが表示されます。
Automation Suiteの環境構築
AWSリソース作成
- これらを手作業で作成するには少々骨が折れるためTerraformにて展開したいと思います。Terraformの使い方に慣れていない方はこちらの記事を参照してTerraformの実行環境をセットアップしてください。
- AWSリソースをTerraformにて作成するためには AWS CLI をインストールします。
- aws configureコマンドでAWS CLIの設定を行います。
- Terraformは作成するリソースの単位、変数およびアウトプットなどによってファイルを分割した方が良いのですが、今回は分かりやすさを優先するためmain.tfという一つのファイルで保存して実行します。各local変数は環境に応じて値を変更します。
- res_prefix: 各AWSリソースのプレフィックス名
- region: リージョン
- availability_zones: アベイラビリティゾーン (DBサブネットのために2つ必要)
- tags: リソースグループに付けるタグ (省略可)
- vpc_address: VPCアドレス範囲
- db_instance_type: RDS for SQL Serverのインスタンスタイプ
- sql_username: RDS for SQL Serverの管理者ユーザー名
- sql_password: RDS for SQL Serverの管理者パスワード
- as_instance_type: WebAPサーバーのインスタンスタイプ (最低でもc5a.4xlarge、インストールする製品数が多い場合はc5a.8xlarge)
- as_instance_name: WebAPサーバーのインスタンス名
- my_ip: 踏み台サーバーにRDPアクセスを許可するIPアドレス (作業マシンがインターネット接続しているグローバルIPアドレス確認して指定します)
- as_fqdn: Automation SuiteのFQDN
main.tf (クリックして展開)
# Local variables (change them according to your environment)
locals {
res_prefix = "hidecha"
region = "ap-northeast-1"
availability_zones = ["ap-northeast-1a", "ap-northeast-1c"]
tags = {
Owner = "hidecha"
Project = "Qiita"
}
vpc_address = "10.1.0.0/16"
db_instance_type = "db.m5d.large"
sql_username = "sql_admin"
sql_password = "SuperSecretPassw0rd"
as_instance_type = "c5a.4xlarge"
as_instance_name = "web01"
my_ip = "104.19.251.9"
as_fqdn = "as.lab.test"
}
# 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 = 2
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 = 2
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)}"
}
}
## 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 = 2
vpc = true
tags = {
Name = "${local.res_prefix}-eip-ngw${format("%02d", count.index + 1)}"
}
}
## NAT Gateways
resource "aws_nat_gateway" "ngw" {
count = 2
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)}"
}
}
## Route table for Public Subnet
resource "aws_route_table" "rt_public" {
vpc_id = aws_vpc.vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
tags = {
Name = "${local.res_prefix}-rt-public"
}
}
## Route Tables for Private Subnets
resource "aws_route_table" "rt_private" {
count = 2
vpc_id = aws_vpc.vpc.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.ngw[count.index].id
}
tags = {
Name = "${local.res_prefix}-rt-private${format("%02d", count.index + 1)}"
}
}
## Route Table to Public Subnets association
resource "aws_route_table_association" "rt_public_assoc" {
count = 2
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 = 2
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"
}
}
# IAM Role (EC2 to S3)
## IAM Role
resource "aws_iam_role" "iam_role" {
name = "${local.res_prefix}-s3-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
},
]
})
}
## IAM Role-Policy attachment
resource "aws_iam_role_policy_attachment" "iam_s3_policy_attach" {
role = aws_iam_role.iam_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess"
}
## IAM Instance Profile
resource "aws_iam_instance_profile" "iam_instance_profile" {
name = "${local.res_prefix}-s3-instance-profile"
role = aws_iam_role.iam_role.name
}
# 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_file" "ssh_key" {
filename = "${aws_key_pair.key_pair.key_name}.pem"
content = tls_private_key.key_pair.private_key_pem
}
# 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*"]
}
}
## Create Windows EC2 Instance for Bastion
resource "aws_instance" "vm" {
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 EC2 Instance
resource "aws_eip" "eip_vm" {
vpc = true
tags = {
Name = "${local.res_prefix}-eip-bastion"
}
}
### Elastic IP to EC2 Instance association
resource "aws_eip_association" "eip_vm_assoc" {
instance_id = aws_instance.vm.id
allocation_id = aws_eip.eip_vm.id
}
# EC2 Instance for Automation Suite
## Get latest RHEL 8.6 AMI
data "aws_ami" "rhel_86" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["RHEL-8.6.0_HVM-*-x86_64-*"]
}
}
## Create RHEL EC2 Instance for Automation Suite
resource "aws_instance" "vm_as" {
ami = data.aws_ami.rhel_86.id
instance_type = local.as_instance_type
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
iam_instance_profile = aws_iam_instance_profile.iam_instance_profile.name
# root disk
root_block_device {
volume_size = 64
volume_type = "gp3"
delete_on_termination = true
encrypted = true
}
# Data disks
ebs_block_device {
device_name = "/dev/sdb"
volume_size = 256
volume_type = "gp3"
delete_on_termination = true
encrypted = true
}
ebs_block_device {
device_name = "/dev/sdc"
volume_size = 512
volume_type = "gp3"
delete_on_termination = true
encrypted = true
}
ebs_block_device {
device_name = "/dev/sdd"
volume_size = 16
volume_type = "io1"
iops = 800
delete_on_termination = true
encrypted = true
}
tags = {
Name = "${local.res_prefix}-${local.as_instance_name}"
}
}
# 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 = 20
max_allocated_storage = 1000
storage_encrypted = false
# 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 = 0
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
}
# Route 53
## DNS Zone
resource "aws_route53_zone" "dns_zone" {
name = local.as_fqdn
vpc {
vpc_id = aws_vpc.vpc.id
}
lifecycle {
ignore_changes = [vpc]
}
}
## DNS records
resource "aws_route53_record" "dns_a_record" {
zone_id = aws_route53_zone.dns_zone.zone_id
name = local.as_fqdn
type = "A"
ttl = 300
records = [aws_instance.vm_as.private_ip]
}
resource "aws_route53_record" "dns_cname_record" {
for_each = toset(["alm", "monitoring", "objectstore", "registry", "insights"])
zone_id = aws_route53_zone.dns_zone.zone_id
name = each.key
type = "CNAME"
ttl = 300
records = [local.as_fqdn]
}
# Outputs
output "bastion_public_ip_address" {
value = aws_eip.eip_vm.public_ip
}
output "web_server_private_ip_address" {
value = aws_instance.vm_as.private_ip
}
output "sqlserver_hostname" {
value = aws_db_instance.sqlserver_instance.address
}
- 準備が整いましたらmain.tfが配置されたディレクトリに移動し、次のコマンドを一行ずつ実行し、Automation Suiteに必要なAWSリソースを展開します。
terraform init terraform plan -out main.tfplan terraform apply main.tfplan
- Terraformによるリソース作成が成功することを確認します。
- AWS管理コンソールにアクセスし、各リソースが作成されていることを確認します。
- Terraformを用いずに手動でAWSリソースを作成することもできます。詳細な手順は 手動: シングル ノードの評価プロファイルの要件とインストール をご参照ください。
Automation Suiteインストール前準備
- AWSリソースが作成できましたら、Automation Suiteのインストールに必要となる設定や前提条件となるパッケージなどをインストールします。
- まず最初に踏み台サーバー(Bastion)にRDPでアクセスし、SSHクライアント(ここでは TeraTerm 5 を使用)をインストールします。Automation SuiteのWebAPサーバーにSSHでアクセスします。
- 次のコマンドを実行してAutomation Suiteに必要となるパッケージをインストールします。
sudo dnf -y install iscsi-initiator-utils nfs-utils rpcbind util-linux nmap-ncat openssl httpd-tools gettext zstd podman bind-utils wget unzip conmon jq lvm2
- ディスクを構成します。
- パーティショニング用スクリプトをダウンロードして実行権限を付与します。
wget https://download.uipath.com/automation-suite/configureUiPathDisks.sh chmod +x configureUiPathDisks.sh
- マウントしているEBSのデバイス名を確認するために
sudo lsblk -dip
を実行します。たとえば次のように出力されます。
/dev/nvme0n1 259:0 0 64G 0 disk
/dev/nvme1n1 259:3 0 512G 0 disk
/dev/nvme2n1 259:4 0 256G 0 disk
/dev/nvme3n1 259:5 0 16G 0 disk- 次のコマンドでパーティショニングを行います。各オプションは
--etcd-disk-name <ディスクサイズ16GB>
,--cluster-disk-name <ディスクサイズ256GB>
,--data-disk-name <ディスクサイズ512GB>
となるようにデバイス名を指定します。
sudo ./configureUiPathDisks.sh --node-type server --install-type online \ --etcd-disk-name /dev/nvme3n1 \ --cluster-disk-name /dev/nvme2n1 \ --data-disk-name /dev/nvme1n1
- 次のコマンドにてNetwork Managerに含まれるnm-cloud-setup.timerとnm-cloud-setup.serviceを無効化します。OS再起動後、再度SSH接続します。
sudo systemctl disable nm-cloud-setup.timer sudo systemctl disable nm-cloud-setup.service sudo reboot
- WebAPサーバーのEC2からS3バケットにアクセスを許可するためのIAMインスタンスプロファイルを作成します。手順はこちらの記事を参照してください。
Automation Suiteインストールの実行
- ここまででようやくAutomation Suiteをインストールする前準備が整いました。WebAPサーバーにSSHでアクセスして次のコマンドを実行し、Automation Suiteのインタラクティブインストーラーを実行します。
wget https://download.uipath.com/automation-suite/2022.10.4/installUiPathAS.sh chmod +x installUiPathAS.sh sudo ./installUiPathAS.sh
- License Agreementに同意します。
- Single-node展開を選択します。
- インストールを継続し、Onlineインストールを選択します。インストールする製品を選択します。
- インストールする製品を番号で指定します。カンマ区切りで複数指定できます。今回はAutomation HubとOrchestratorをインストールするため
4,9
と指定します。製品選択を確認の上、続行します。
- Automation SuiteのFQDN(例: as.lab.test)を入力し、オブジェクトストアとしてAmazon S3を指定します。
- 次にSQL Serverへの接続情報を入力します。Kerberos認証は使用せず、データベースは自動作成するように設定します。
- サーバー証明書のルート証明書のパスを訊かれますが空白で続行します。サーバー証明書はインストール後に入れ替えが可能です。
- インストールオプションの確認画面が表示されます。問題なければ続行します。(設定変更も可能です)
- インストールオプションは構成ファイルとして
/opt/UiPathAutomationSuite/cluster_config.json
に保存されます。このファイルを編集して詳細にインストールオプションをカスタマイズすることもできます。詳細は 手動: 高度なインストール をご参照ください。 - 最終確認の画面が表示されますので、続行するとインストールが開始されます。
- インストールは1~2時間ほどかかります。途中でエラーが発生した場合にはエラーメッセージに出力されるインストールログファイルを確認します。
- インストールが完了すると、管理者パスワードの取得方法などの手順が表示されます。
Automation Suiteインストール後の作業
Automation Suite管理画面へのログイン
- ログインのためのパスワードを取得するためにWebAPサーバーにSSHでアクセスして次のコマンドを実行します。デフォルト組織とホスト組織の管理者の初期パスワードは同じです。初回ログイン時に変更することをお勧めします。
sudo su - export KUBECONFIG=/etc/rancher/rke2/rke2.yaml PATH=$PATH:/var/lib/rancher/rke2/bin kubectl get secrets/platform-service-secrets -n uipath -o "jsonpath={.data['identity\.hostAdminPassword']}" | base64 --decode
- ホスト組織は組織を管理するための特別な組織です。ホスト組織にシステム管理者でログインして、新しい組織を作成したり、ライセンスを一元管理して各組織に割り当てることができます。詳細はホストの管理 ガイドの各ページをご参照ください。
- 今回は自動的に作成されるデフォルト組織にログインしてみます。
- Orchestrator管理画面にアクセスしてみます。Automation Cloudとほぼ同じ操作感です。
- Automation Hubの管理画面にアクセスするにはライセンスアクティベーションが必要です。
- トライアルライセンスなどについてはUiPath社にお問い合わせください。
- ライセンスモデルについてはWebガイドをご参照ください。
- ライセンスコードを入手後、アクティベーションを行います。
- Automation Hubが含まれるライセンスをアクティベーションしますと、DefaultTenantのサービス画面にてAutomation Hubのサービスを追加できるようになります。
- 以前はAutomation Cloudでのみ利用可能だったAutomation Hubが自前のAWS環境でも構築することができました。
- ユーザーの言語設定によってUIを日本語に切り替えることも可能です。
サーバー証明書の入れ替え
- Automation Suiteのインストール時に自動作成される自己署名証明書の有効期限は90日間です。有効期限が切れる前に別のサーバー証明書を準備し、入れ替えを行う必要があります。詳細な手順は 証明書を管理する をご参照ください。
おわりに
- 今回は難易度が高いと言われているAutomation SuiteのインストールをAWS環境にて実行する手順を説明しました。
- UiPathのすべての製品を一つの環境で展開できるのがAutomation Suiteの大きなメリットです。ご関心のある方はぜひAutomation Suiteのインストールにチャレンジしていただけると幸いです!