ROSA with Hosted Control Plane の Tech Preview

Red Hatさんのご厚意により、OpenShiftのマネージドサービスであるROSAのHosted Control Planeの機能をお試しさせてもらうことになりました。先週あった、Red Hat Summitでも[当該機能のPublic Preview開始のアナウンス](https://www.redhat.com/en/blog/making-cloud-native-more-accessible-red-hats-vision-red-hat-openshift-hosted-control-planes)がありました。この記事では、ROSA with HCPをインストールしてみて、作成されるリソースをみていきたいと思います。

## ROSA with Hosted Control Plane

Hosted Control Plane（HCP）は OpenShift 関連製品で提供予定の技術です。Self Managed 版のOpenShift の他、ROSA でも提供予定とされており、Self Managed 版のOpenShift 4.12では、Tech Previewとなっています。ROSAはHCPを使用することで、専用のコントロールプレーンがRed HatによりManaged Serviceとして提供され、より効率的かつ信頼性の高いアーキテクチャが実現できるとされています。

ただし、ROSA with HCPを利用する上では、以下の点の留意が必要です。

* 既にROSAをご自身のAWSアカウント上で展開している場合で、ROSA with HCPの機能を利用するためには、既存のクラスターを「アップグレード」することはできないため、新しいクラスターを作成する必要があります。
* すべてのROSA with HCPクラスターには、AWS Security Token Service (STS)の有効化が必要です。
	

## これまでのROSAとの比較

ROSA with HCPに於けるControl Plane（Master Node）は、Red HatにManagedされるため、実際に自分達のAWSアカウント内に構築されるNodeはWorker Nodeのみとなります。そのため、数分で新しいクラスターを迅速に作成し、アプリケーションを展開することができます。これまでのROSAでもそうでしたが、ROSA with HCPでもWorker Nodeは最小2ノードのみを必要とし、同時に大規模なプロジェクトや企業にむけたスケールアウトも可能となっています。

これまでのROSAとROSA with HCPの違いは以下の通りです。

* インストールパス
    - HCP：etcd、APIサーバー、OAuthなどのコントロールプレーンのコンポーネントが別のAWS上に展開され、Red Hatが所有および管理するアカウント内にホストされます。
    - Classic：コントロールプレーンのコンポーネントとインフラストラクチャ、ワーカーノードが顧客の同じAWSアカウント内で一緒に展開されます。ただし、Red Hatが管理します。
* プロビジョニング時間
    * HCP：約10分
    * Classic：約40分
* アーキテクチャ
    * HCP：エンドカスタマーが直接アクセスできないように、コントロールプレーンのインフラストラクチャは完全に管理されます。
    * Classic：顧客がコントロールプレーンとAWSインフラストラクチャをホストする責任がありますが、Red Hatによって管理されます。
* フットプリント
    * HCP：1つのクラスターには最低2つのノードが必要です。
    * Classic:1つのクラスターには最低7つのノードが必要です。（Master Node と Infra Nodeを含む）
* デプロイメント
    * HCP：ROSA CLIまたはWeb UIを使用して展開します。
    * Classic：ROSA CLIまたはWeb UIを使用して展開します。
* アップグレード
    * HCP：選択的にコントロールプレーンとマシンプールを別々にアップグレードできます。
    * Classic：クラスター全体を一度にアップグレードします。
* リージョンの可用性：
	* HCP:eu-central-1、eu-west-1、us-east-1、us-east-2、us-west-2で利用可能です。
    * Classic:AWSが利用可能なすべてのリージョンで利用可能です。
* コンプライアンス
    * HCP：GA後にコンプライアンス認証が計画されています。
	* Classic：FIPSコンプライアンスはまだ利用できません。ISO 27001、17、18、SOC 2 Type 2、SOC 3、PCI-DSS、HIPAAの準拠があります。

## ROSA with HCPのセットアップ

前置きは以上とし、セットアップしていきましょう。

### セットアップのゴール

* HCPモードでROSAを動かす
* Red Hat側のコントロールプレーンとはPrivate Link経由で接続する（インターネットにエンドポイントを晒さない）
* そのため、ROSAはPrivate Subnetに配置する
* そのため、ROSAがインターネットとのアクセスをするときはForward Proxy経由とする
* コントロールプレーン上のetcdは、自身のAWSアカウント上で管理されているKMSの鍵で暗号化する
* 他のオプションはデフォルトとする

### 前提条件

* [AWS prerequisites for ROSA](https://docs.openshift.com/rosa/rosa_install_access_delete_clusters/rosa_getting_started_iam/rosa-aws-prereqs.html)の実施

### AWSリソースの作成

CloudFormationを使って、ROSAをインストールする環境をセットアップします。

* VPCと2つのサブネットを作成する（PrivateとPublic）
* EC2をそれぞれのサブネットに配置して、PublicのEC2にはApacheをプロキシ構成でインストールする
* 後ほど、ROSAのインストールで使うパラメータをOutputする

```yaml:rosahcp-cfn.yaml
AWSTemplateFormatVersion: 2010-09-09

Parameters:
  RegionParameter:
    Type: String
    Default: ap-northeast-1
    AllowedValues:
      - ap-northeast-1
      - ap-northeast-2
      - ap-northeast-3
      - us-east-1
      - us-east-2
  Ec2ImageId:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
  Ec2InstanceType:
    Type: String
    Default: t3.nano
  KeyPair:
    Type: AWS::EC2::KeyPair::KeyName
    Description: EC2 Key Pair
  MyIP:
    Type: String
    Description: Your IP address (e.g., 203.0.113.1)

Resources:
  CfVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: 'true'
      EnableDnsHostnames: 'true'
      Tags:
        -
          Key: Name
          Value: CfVPC

  CfPublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: true
      VpcId: !Ref CfVPC
      AvailabilityZone: !Join
      - ''
      - - !Ref RegionParameter
        - 'a'
      Tags:
        - Key: Name
          Value: CfPublicSubnet
  CfPrivateSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.2.0/24
      MapPublicIpOnLaunch: false
      VpcId: !Ref CfVPC
      AvailabilityZone: !Join
      - ''
      - - !Ref RegionParameter
        - 'a'
      Tags:
        - Key: Name
          Value: CfPrivateSubnet

  CfInternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: CfInternetGateway

  AttachCfInternetGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId : !Ref CfInternetGateway
      VpcId: !Ref CfVPC

  CfRouteTableForPublicSubnet:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref CfVPC
      Tags:
        - Key: Name
          Value: CfRouteTableForPublicSubnet

  CfRouteForPublicSubnet:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref CfRouteTableForPublicSubnet
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref CfInternetGateway

  CfAssocciateRouteTableForPublicSubnet:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref CfRouteTableForPublicSubnet
      SubnetId: !Ref CfPublicSubnet

  NatGatewayEIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

  CfNatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId:
        Fn::GetAtt:
          - NatGatewayEIP
          - AllocationId
      SubnetId: !Ref CfPublicSubnet
      Tags:
        - Key: Name
          Value: CfNatGateway

  CfRouteTableForPrivateSubnet:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref CfVPC
      Tags:
        - Key: Name
          Value: CfRouteTableForPrivateSubnet

  CfRouteForPrivateSubnet:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref CfRouteTableForPrivateSubnet
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref CfNatGateway

  CfAssocciateRouteTableForPrivateSubnet:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref CfRouteTableForPrivateSubnet
      SubnetId: !Ref CfPrivateSubnet

  CfPublicSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow SSH and all traffic from VPC
      VpcId: !Ref CfVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: !Ref MyIP
        - IpProtocol: -1
          FromPort: -1
          ToPort: -1
          CidrIp: 10.0.0.0/16

  CfPrivateSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow all traffic from VPC
      VpcId: !Ref CfVPC
      SecurityGroupIngress:
        - IpProtocol: -1
          FromPort: -1
          ToPort: -1
          CidrIp: 10.0.0.0/16

  CfPublicEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref Ec2ImageId
      InstanceType: !Ref Ec2InstanceType
      KeyName: !Ref KeyPair
      SubnetId: !Ref CfPublicSubnet
      SecurityGroupIds:
        - !Ref CfPublicSecurityGroup
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          yum update -y
          yum install -y httpd
          tee /etc/httpd/conf.d/proxy.conf <<EOF
          Listen 3128
          <IfModule proxy_module>
            ProxyRequests On
            ProxyVia      On
            ProxyTimeout  300
            AllowCONNECT 443
            CustomLog logs/proxy_log combined
          
            <Proxy *>
              Order allow,deny
              Allow from all
            </Proxy>
          </IfModule>
          EOF
          systemctl enable httpd
          systemctl start httpd
      Tags:
        - Key: Name
          Value: PublicEC2Instance

  CfPrivateEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref Ec2ImageId
      InstanceType: !Ref Ec2InstanceType
      KeyName: !Ref KeyPair
      SubnetId: !Ref CfPrivateSubnet
      SecurityGroupIds:
        - !Ref CfPrivateSecurityGroup
      Tags:
        - Key: Name
          Value: PrivateEC2Instance

Outputs:
  PublicEC2InstancePublicIP:
    Description: "Public IP of the Public EC2 instance"
    Value: !GetAtt CfPublicEC2Instance.PublicIp

  PublicEC2InstancePrivateIP:
    Description: "Private IP of the Public EC2 instance"
    Value: !GetAtt CfPublicEC2Instance.PrivateIp

  PrivateEC2InstancePrivateIP:
    Description: "Private IP of the Private EC2 instance"
    Value: !GetAtt CfPrivateEC2Instance.PrivateIp

  PrivateSubnetId:
    Description: "Subnet ID of the private subnet"
    Value: !Ref CfPrivateSubnet
```

`create-stack`します。**MyIP**には、作業環境のグローバルIPアドレスをいれます。

```bash
% aws cloudformation create-stack --stack-name rosahcp ¥
  --parameters ParameterKey=KeyPair,ParameterValue=rosahcp-vpc ¥
               ParameterKey=MyIP,ParameterValue=xxx.xxx.xxx.xxx./32 ¥
               ParameterKey=RegionParameter,ParameterValue=us-east-1 ¥
  --template-body file://rosahcp-cfn.yaml
```

### Create STS roles and policies

ROSAが利用する各種IAM RoleとIAM Policyを作成していきます。

```zsh
# Account Roleの作成
% rosa create account-roles --mode auto
I: Logged in as '...snip...' on 'https://api.openshift.com'
...snip...
I: To create a cluster with these roles, run the following command:
	rosa create cluster --sts

# OIDC Configの作成
% rosa create oidc-config --mode auto
? Would you like to create a Managed (Red Hat hosted) OIDC Configuration Yes
...snip...
? Create the OIDC provider? Yes
...snip...
I: Created OIDC provider with ARN 'arn:aws:iam::68 ...snip... 92:oidc-provider/rh-oidc.s3.us-east-1.amazonaws.com/23 ...snip... 5d'

# Operator Roleを作成
% rosa create operator-roles --prefix rosahcp --oidc-config-id 23 ...snip... 5d
? Role creation mode: auto
? Operator roles prefix: rosahcp
? Create hosted control plane operator roles (optional): Yes
...snip...
? Permissions boundary ARN (optional): 
...snip...
I: To create a cluster with these roles, run the following command:
	rosa create cluster --sts --oidc-config-id 23 ...snip... 5d --operator-roles-prefix rosahcp --hosted-cp
```

ここで、`rosa create oidc-config`というコマンドを実行しました。
コントロールプレーンをRed Hatがマネージドすることとなったため、IDトークンの署名用の秘密鍵の所在や、`/openid-configuration`のエンドポイントの所在や、そもそものOpenID Providerの持ち主を決めたりするのかもしれません。2023/5下旬時点では、あまりドキュメンテーションされていないので、[こちらのページ](https://docs.openshift.com/rosa/rosa_hcp/rosa-hcp-sts-creating-a-cluster-quickly.html)の更新を待ちましょう。

### 暗号化用の鍵をKMSに作成

コントロールプレーンの管理がRed Hatになったので、etcdも自ずとRed Hat管理となります。etcdには秘密の情報がsecretに格納されたりしますので、暗号化は必須と言えます。面倒くさがらずに、ちゃんと暗号化しましょう。ロールの名前に`rosahcp`とはいっていますが、Operator Roleを作成した時のprefixを入れるようにしましょう。

```bash
# Key Policyのドキュメントを準備
% cat rosahcp-kms-policy.yaml 
{
    "Version": "2012-10-17",
    "Id": "key-default-1",
    "Statement": [
        {
            "Sid": "Enable IAM User Permissions",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::NNNNNNNNNNNN:role/ManagedOpenShift-Installer-Role",
                    "arn:aws:iam::NNNNNNNNNNNN:role/rosahcp-kube-system-kms-provider",
                    "arn:aws:iam::NNNNNNNNNNNN:role/rosahcp-openshift-cluster-csi-drivers-ebs-cloud-credentials",
                    "arn:aws:iam::NNNNNNNNNNNN:role/rosahcp-kube-system-capa-controller-manager",
                    "arn:aws:iam::NNNNNNNNNNNN:root"
                ]
            },
            "Action": "kms:*",
            "Resource": "*"
        }
    ]
}

# 鍵の作成
% aws kms create-key --policy file://rosahcp-kms-policy.yaml
{
    "KeyMetadata": {
        "AWSAccountId": "NNNNNNNNNNNN",
        "KeyId": "64 ...snip... bb",
        "Arn": "arn:aws:kms:us-east-1:NNNNNNNNNNNN:key/64 ...snip... bb",
        "CreationDate": "2023-05-29T22:54:54.994000+09:00",
        "Enabled": true,
        "Description": "",
        "KeyUsage": "ENCRYPT_DECRYPT",
        "KeyState": "Enabled",
        "Origin": "AWS_KMS",
        "KeyManager": "CUSTOMER",
        "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT",
        "KeySpec": "SYMMETRIC_DEFAULT",
        "EncryptionAlgorithms": [
            "SYMMETRIC_DEFAULT"
        ],
        "MultiRegion": false
    }
}
```

鍵の作成時（`aws kms create-key`）のアウトプットのうち、Arnは`rosa create cluster`するときに使うので、コピーしときましょう。


### ROSA with HCPの作成

前提が整ったので、`rosa create cluster`しましょう。

```bash
# ROSAをインストールする先のサブネットのIDを取得
% aws cloudformation describe-stacks --stack-name rosahcp ¥
  --query 'Stacks[0].Outputs[?OutputKey==`PrivateSubnetId`].OutputValue' ¥
  --output text 

# ROSAが使うProxyのIPを取得
% aws cloudformation describe-stacks --stack-name rosahcp ¥
  --query 'Stacks[0].Outputs[?OutputKey==`PublicEC2InstancePrivateIP`].OutputValue' ¥
  --output text

# ROSA with HCPの作成 
rosa create cluster \
  --cluster-name rosahcp \
  --sts \
  --role-arn arn:aws:iam::NNNNNNNNNNNN:role/ManagedOpenShift-Installer-Role \
  --support-role-arn arn:aws:iam::NNNNNNNNNNNN:role/ManagedOpenShift-Support-Role \
  --worker-iam-role arn:aws:iam::NNNNNNNNNNNN:role/ManagedOpenShift-Worker-Role \
  --operator-roles-prefix rosahcp \
  --oidc-config-id 24 ...snip... e7 \
  --region us-east-1 \
  --version 4.12.18 \
  --enable-autoscaling \
  --min-replicas 2 \
  --max-replicas 2 \
  --compute-machine-type m5.xlarge \
  --machine-cidr 10.0.0.0/16 \
  --service-cidr 172.30.0.0/16 \
  --pod-cidr 10.128.0.0/14 \
  --host-prefix 23 \
  --private-link \
  --subnet-ids subnet-0f2d12e7d7b1d10f2 \
  --etcd-encryption \
  --http-proxy http://10.0.1.13:3128/ \ #一つ前の手順のPublicEC2InstancePrivateIP
  --https-proxy http://10.0.1.13:3128/ \ #一つ前の手順のPublicEC2InstancePrivateIP
  --no-proxy "10.0.0.0/16" \
  --kms-key-arn arn:aws:kms:us-east-1:NNNNNNNNNNNN:key/64 ...snip... bb \ #さっきコピーしたKMSのArn
  --hosted-cp \
  --etcd-encryption-kms-arn arn:aws:kms:us-east-1:NNNNNNNNNNNN:key/64 ...snip... bb #さっきコピーしたKMSのArn

# インストール後のログ確認（10分程度で終わる）
% rosa logs install -c rosahcp --watch
I: Cluster 'rosahcp' has been successfully installed
```

## インストール後の探索

### cluster-adminの作成

```bash
% rosa create admin --cluster=rosahcp
oc login https://api.rosahcp.1gjb.p3.openshiftapps.com:443 --username cluster-admin --password i6 ...snip... 6F

```

ここで、`oc login`の時に利用するURLが還元されますが、このURLは利用できません。
PrivateLinkを有効にするセットアップとしているので、プライベートからしかアクセスできないようになっています。

`hypershift.local`というHosted Zoneが切られています。

![スクリーンショット 2023-05-20 23.01.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325101/219385ec-f775-b3c8-2c30-3166cae2f197.png)

その中の`api.rosahcp.hypershift.local`が、その対象となっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325101/9dd6f7f1-ef80-c584-5517-6f9ae73f1558.png)

### VPC EndpointのSecurity Groupの設定変更

ROSAクラスタが作成されると、ROSAを配置したサブネットにVPC Endpointがくっついています。先ほどの`api.rosahcp.hypershift.local`は、このVPC EndpointにCNAMEが切られています。現時点ではこのVPC EndpointのSecurity GroupのインバウンドルールはデフォルトのSecurity Groupからのアクセスのみを許可する設定となっているので、変更しましょう。（変更しないと、このあとの`oc login`ができないです）

```bash
# VPC EndpointのIDを取得する
% aws ec2 describe-vpc-endpoints --query 'VpcEndpoints[].VpcEndpointId' --output text
vpce-0d9fba9f6cc0616da

# そのVPC Endpointに設定されているSecurity GroupのIDを取得する
% aws ec2 describe-vpc-endpoints ¥
  | jq --raw-output '.VpcEndpoints[] ¥
  | select(.VpcEndpointId == "vpce-0d9fba9f6cc0616da") ¥
  | .Groups[0].GroupId'
sg-04e148ded114f8b6c

# VPC内からのアクセスを許可する
% aws ec2 authorize-security-group-ingress --group-id sg-04e148ded114f8b6c --protocol all --cidr 10.0.0.0/16
```


### `oc get nodes`しよう

それでは、`oc get nodes`をしに行きましょう。
先ほども触れた通り、インターネット経由で`oc`コマンドは実行できないので、Private Subnetに展開したEC2まで移動する必要があります。入力するパラメータはCloudFormationのOutputから適宜拾ってください。

```bash
# 多段SSHの実行
ssh -o ProxyCommand='ssh -i ~/Downloads/rosahcp-vpc.pem -W %h:%p ec2-user@<PublicEC2InstancePublicIP>' -i ~/Downloads/rosahcp-vpc.pem ec2-user@<PrivateEC2InstancePrivateIP>

# rosaコマンドのダウンロードと展開
curl -L https://mirror.openshift.com/pub/openshift-v4/clients/rosa/latest/rosa-linux.tar.gz ¥
  | tar -xz -C /tmp ¥
  && sudo mv /tmp/rosa /usr/local/bin ¥
  && sudo chmod +x /usr/local/bin/rosa

# ocコマンドのダウンロードと展開
$ tar -zxvf ./openshift-client-linux.tar.gz -C /tmp/ ¥
  && sudo mv /tmp/oc /usr/local/bin/ ¥
  && sudo chmod +x /usr/local/bin/oc

# バージョンの確認
$ oc version
Client Version: 4.13.0
Kustomize Version: v4.5.7

# oc login
$ oc login https://api.rosahcp.hypershift.local:443 --username cluster-admin --password ...snip...
...snip...
Login successful.

# oc get nodes
$ oc get nodes
NAME                         STATUS   ROLES    AGE    VERSION
ip-10-0-2-169.ec2.internal   Ready    worker   2m2s   v1.25.8+37a9a08
ip-10-0-2-35.ec2.internal    Ready    worker   2m4s   v1.25.8+37a9a08
```

Master Nodeが見えないようになっています！

## おわりに

今回はPrivate Tech Preview中であるROSAのHosted Control Planeのインストールをお試ししてみました。コントロールプレーンをRed Hatがマネージドしてくれることで、コストを抑えられますし、何より起動時間がとても短くなったのが嬉しいです。m5.xlargeが2台で構成できるので、お財布にも優しいです。
また、大きな企業の場合、AWSアカウントは[Landing Zone](https://docs.aws.amazon.com/ja_jp/prescriptive-guidance/latest/migration-aws-environment/understanding-landing-zones.html)のような構成を取るでしょうし、これまでのROSAやOCPを展開するときは、お金の都合で複数システム相乗りとなりがちだったのかなとも想像します。ROSA with HCPを使うことで、AWSのPrescriptive Guidanceに沿った実装に近づけることができるとも思います。
ROSAの中の人曰く、GAは2023Q3を予定しているそうです。楽しみです。:yum: