Azure Red Hat OpenShiftだけどS3使いたい、ただしAWSアカウントは作成しないものとする

# ご注文はS3ですか？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/5d20e13e-809e-37db-f6b2-6dabc8a784c9.png)

「[Amazon S3](https://aws.amazon.com/jp/pm/serv-s3/?gclid=EAIaIQobChMI9b-fssKKiQMVJ-YWBR3fXwToEAAYASAAEgL8NPD_BwE&trk=7307ac43-21ab-472b-91fc-d397a3762850&sc_channel=ps&ef_id=EAIaIQobChMI9b-fssKKiQMVJ-YWBR3fXwToEAAYASAAEgL8NPD_BwE:G:s&s_kwcid=AL!4422!3!530706637385!e!!g!!aws%20s3!13707437647!129922688371)」はクラウドネイティブなオブジェクトストレージとして非常に幅広く使われています。S3互換を謳う様々なオブジェクトストレージサービス、オープンソースのプロジェクトも多く存在します。

AWSと競合するサービスもS3互換APIや乗り換え機能を提供しています。
- [IBM Cloud Object Storage](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-compatibility-api)
- [Google Cloud Storage](https://cloud.google.com/storage/docs/migrating?hl=ja)

個人的には「クラウドネイティブなオブジェクトストレージ＝S3」と言えるくらいに幅広いシーンで利用され、意識されていると考えています。こんなS3を「Azure Red Hat OpenShift」でも使いたいのです。

ただしAWSアカウントは一切作成せずに。

:::note
「[Azure Red Hat OpenShift](https://azure.microsoft.com/ja-jp/products/openshift)」はAROと略され、Microsoft AzureのManaged Serviceとして「Red Hat OpenShift」を利用できるソリューションです。もし自社のプライマリクラウドがAzureなのであれば、Auzreの利用料金と一緒に使った分だけの従量課金でOpenShiftを利用できるので、請求も含めて一元的に管理できます。加えて、[Microsoft Entra ID](https://www.microsoft.com/ja-jp/security/business/identity-access/microsoft-entra-id)（旧Azure Active Directory）との連携はもちろん、[Azure OpenAI Service](https://azure.microsoft.com/ja-jp/products/ai-services/openai-service)を含め、各種のAzure Serviceとの連携にはAROがバッチリハマるでしょう。

AROは、国内でも[三菱重工](https://customers.microsoft.com/ja-jp/story/1591363685728024633-mitsubishi-heavy-industries-ltd-discrete-manufacturing-azure-ja-japan)や[明治安田生命](https://ms-f1-sites-03-ea.azurewebsites.net/en-us/story/1797986399213231729-meijiyasuda-insurance-azure-ja-japan)といった名高い企業さんで利用されているようです。
:::

# OpenShift Data Foundationを使う
[OpenShift Data Foundation](https://www.redhat.com/ja/technologies/cloud-computing/openshift-data-foundation)（略してODF）を使えば、OpenShift上でSoftoware Defined Storageを簡単に展開できます。OpenShiftのOperatorHubから「[OpenShift Data Foundation Operator](https://docs.redhat.com/ja/documentation/red_hat_openshift_data_foundation/4.16/html/red_hat_openshift_data_foundation_architecture/openshift_data_foundation_operators)」をインストールし、カスタムリソースを作成すれば、ブロックボリューム、ファイルストレージ、オブジェクトストレージが`StorageClass`として提供されます。

::: note
AROにおけるODFの利用は内部モード（クラスタ内ワークロードのためのSDS）として利用することができます。
https://access.redhat.com/articles/4731161
:::

ODFはざっくり2つの要素から構成されています。
- Rook-Ceph
- Noobaa

Rook-Ceph は、Kubernetes上でオブジェクト、ブロック、ファイルストレージを提供できる統合ストレージシステムとして、スケーラブルで高可用性な分散ストレージが必要な場合に適しています。NooBaa は、S3互換APIを提供し、マルチクラウドやハイブリッドクラウド環境でオブジェクトストレージを柔軟に管理できるため、クラウドネイティブなS3ストレージが求められるケースに最適とされています。

今回の問「AROを使っているけどS3使いたい」を達成する為に、この「Noobaa」を使います。

# やってみる
まずは「えいや！」でARO環境を用意しました。なお、本環境のOpenShiftのバージョンは「4.15.27」です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/bc7acfdd-a4a7-2eb0-d6fd-4f8c760e7eb2.png)

OpenShiftにログインした後はいつものOpenShiftです。Azureだからどうという事はありません。ただし、`StorageClass`にはAzureのストレージが見えていますので、この辺はAzureに高度に統合されている感を感じます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/fce56be1-f0f6-dc10-cc53-975c70f80262.png)

OperatorHubで「odf」と検索し、でODF Operatorをインストールします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/6c3cf7bd-c7a3-862a-0e6e-7300aa087c07.png)

インストールが完了するとWebコンソールを更新するように通知が来ますので、更新してください。そうするとODF用のGUIが有効になります。なお、ODF Operatorが提供する各種のコンポーネントはNameSpace「openshift-storage」にDeployされ、トポロジー画面でも確認できます。今回の主役はこの中の「Noobaa Operator」です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/62bbdce4-11dc-009b-1061-a85bc20150cc.png)

`StorageSystem`CRの作成が求められますので作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/3c8512e8-aa02-392c-d467-fa63c614d184.png)

通常ODFを利用する際にはフル機能が利用できる「Full deployment」が選択される場合がほとんどですが、もしオブジェクトストレージのみを利用したい場合は「MultiCloud Object Gateway」を選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/cfeabd57-a203-fb09-c6a7-1209f0d6c887.png)

:::note 
MultiCloud Object Gateway（MCG）についての詳細はRed Hatの宇都宮さんが公開してくださった資料を御覧ください。下手に私が説明するよりこれをお読み頂く方が、最もフィジカルで、最もプリミティブで、そして最もロジカルに理解させていただけます。
https://speakerdeck.com/tutsunom/data-federation-with-mcg
:::

MCGを選んだら、自動的にバッキングストレージにプラットフォームに最適な`StorageClass`が選ばれています。今回はAzureがプラットフォームなので、先程`StorageClass`一覧で確認した「managed-csi」が選ばれますので、これ以降、そのままの設定で進めます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/7b13d50b-bb51-475c-2fde-6ddc72913724.png)

最後に「StorageSystemの作成」をクリックすればNoobaaがDeployされるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/76bc860b-de29-4719-08e6-526b047f2cc1.png)

トポロジー画面でNoobaaのDeploy状況を確認することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/affe3641-6282-6949-cbfd-75aae75a800d.png)

Noobaa本体を提供するStatefulSet「noobaa-core」とバックエンドDBを提供するStatefulSet「noobaa-db-pg」、そしてS3 Endpointを提供するDeployment「noobaa-endopoint」です。

カスタムリソース「noobaa」のYAMLファイルも確認してみます。

```noobaa.yaml
（中略）
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: cluster.ocs.openshift.io/openshift-storage
                operator: Exists
（中略）
  tolerations:
    - effect: NoSchedule
      key: node.ocs.openshift.io/storage
      operator: Equal
      value: 'true'
```
ODFをインストールするとNodeに自動的に振られるラベル「`cluster.ocs.openshift.io/openshift-storage: ''`」が存在するNodeに各コンポーネント（noobaa-core, noobaa-db-pg, noobaa-endopoint）をスケジュールする様に、nodeAffinityルールが設定されています。


「noobaa-endpoint」がまさに文字通り、S3のエンドポイントを提供してくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/e1d85f17-3a34-a398-04b5-6c0c3d58f2ae.png)

クラスタ外部に対してはRoute、クラスタ内部に対してはServiceを宛先として提供します。この辺は非常にOpenShiftネイティブでわかりやすいですね。

ではさっそくバケットを作成します。バケットはカスタムリソース`ObjectBucketClaim`を作成することで、指定した`BucketClass`からプロビジョニングされます。この辺の雰囲気は、「`PersistentVolumeClaim`を作成することで、指定した`StorageClass`から永続ボリューム`PersistentVolume`がプロビジョニングされる流れ」と全く同様、相似形として理解できます。

すでにOpenShiftコンソール画面の「管理者向け表示」の左メニュー「ストレージ」から「Object Storage」が選択できるようになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/0cd1e270-03d9-eef0-8982-82fd395c6913.png)

ここをクリックすると、NoobaaのDeployと共に利用できる様になった`BucketClass`が確認できます。タブ「オブジェクトバケット要求」に切り替えて、`ObjectBucketClaim`を作成します。なお、`ObjectBucketClaim`は`PersistentVolumeClaim`と同様、NameSpaceリソースです。なにかS3バケットを使いたいと思っているクライアントアプリケーションをDeployしているNameSpace（例：hogehoge）に切り替えて`ObjectBucketClaim`を作成しましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/907f65cb-210f-ce1a-3f43-a2e74342b36f.png)

こんな感じでGUIで作成できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/1fa273af-e4e0-ec9f-6653-1b3fa0ea15e0.png)

クラスタ内で利用できる`StorageClass`や`BucketClass`を選び、「作成」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/0852cf57-2b0b-cc43-995f-8c6f64311b55.png)

できました。これでS3バケットにアクセスする際に必要な情報一式が払い出されます。`ObjectBucketClaim`によってバケットが作成されたことも確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/95d418e7-de85-5d1c-a9bb-6ae0ecf3b28e.png)

さて、このアクセス情報一式を使って、AWS CLIからバケットにアクセスしてみます。

# AWS CLIの設定
お使いのPCにAWS CLIをインストールし、AWSのアクセスキー・シークレットキーを設定します。ここで設定する情報は、今`ObjectBucketClaim`によって払い出されたものを指定します。

```sh
~ % aws configure                                                                      
AWS Access Key ID [***]: Access Key
AWS Secret Access Key [***]: Secret Key
Default region name [***]: us-east-1 
Default output format [json]: 
```
リージョンは特になんでもお好きなものを指定してOKです。とりあえず`us-east-1`としておきました。ではAWSコマンドでS3（と完全互換のNoobaaによって作成された）バケットにアクセスしてみましょう。

```sh
aws s3 ls --endpoint-url <Routeから払い出されたURL>
2024-10-13 15:32:30 my-hogehoge-bucket-ca6219b8-aba2-43a8-a75a-29442dc1bd42
2024-10-13 15:32:30 first.bucket
```
おー！`ObjectBucketClaim`作成時に払い出された「Bucket Name」と同じバケットを認識できました。なにか適当なファイル（exaple.txt）について、バケット内にディレクトリ「my-first-directory」を作成し、その中にアップロードしてみます。

```sh
~ % aws s3 cp example.txt s3://<Bucket Name>/my-first-directory/example.txt --endpoint-url <Routeから払い出されたURL>
```

今作成・アップロードしたファイルをS3コマンドで確認します。

```sh
~ % aws s3 ls s3://my-hogehoge-bucket-ca6219b8-aba2-43a8-a75a-29442dc1bd42/ --endpoint-url https://s3-openshift-storage.apps.bm21j8xxb786a2add5.eastus.aroapp.io
                           PRE my-first-directory/
~ % aws s3 ls s3://my-hogehoge-bucket-ca6219b8-aba2-43a8-a75a-29442dc1bd42/my-first-directory/ --endpoint-url https://s3-openshift-storage.apps.bm21j8xxb786a2add5.eastus.aroapp.io
2024-10-13 15:37:47         12 example.txt
```

# おわりに
クライアントアプリケーションからすると、Amazon S3を使っているのと全く同じ感覚で、ARO上に作成されたバケットにアクセスしたり操作できたりすることがわかりました。AWSが全く絡んでこないにもかかわらず、AWS CLIで操作できる点が非常に面白いですね。

AzureにもAmazon S3に該当するサービス「[Azure Blob Storage](https://azure.microsoft.com/ja-jp/products/storage/blobs)」が存在しますが、普段Azureを触っていないアプリケーション開発者からすると、使い勝手の理解に際して認知不可が高いと思われます。私も正直普段全くAzureを触っていないので一から調べるのはしんどいです。

一方でOpenShiftを使っていると何かとオブジェクトストレージは必要です。
- [OpenShift LoggingでLokiスタックを利用する場合](https://docs.openshift.com/container-platform/4.16/observability/logging/log_storage/installing-log-storage.html)
- [OpenShift AIにてData Connectionの設定を行う場合](https://docs.redhat.com/en/documentation/red_hat_openshift_ai_self-managed/2-latest/html/working_on_data_science_projects/using-data-connections_projects#adding-a-data-connection-to-your-data-science-project_projects)
- 等々

その点、「S3と全く同じ開発者体験ですよ」を謳えるNoobaa及びOpenShift Data Foundationは非常に有用な気がします。

:::note
`ObjectBucketClaim`を作成時、Access KeyとSecret Keyは`ObjectBucketClaim`を作成したNameSpace内にSecretリソースとして作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/8c11d6ed-6ab3-75d0-f881-4012a69ff4ad.png)

Secretの中身をを見てみるとこんな感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/cf432d69-64ea-1c05-a21e-a4e115eedc65.png)

```yaml
data:
  AWS_ACCESS_KEY_ID: <Access KeyをBASE64エンコードしたもの>
  AWS_SECRET_ACCESS_KEY: <Secret KeyをBASE64エンコードしたもの>
```
Access KeyとSecret Key、それぞれの値（Value）を参照するためのKeyが、それぞれAWS_ACCESS_KEY_IDとAWS_SECRET_ACCESS_KEYになっています。つまり、なんらかバケットにアクセス・操作したいクライアントアプリケーション側は、このSecretを参照すればよいということです。その際にはまるでAmazon S3を使うがごとく同Keyを指定すればよいということです。徹頭徹尾、「S3と全く同じ開発者体験ですよ」に即していますね。
:::