Oracle Database Operator for KubernetesでShardingをやってみる

# はじめに

Oracleでは昨年、Oracle Databaseをk8s上で動作させるための手段として、[Kubernetes Operatorパターン](https://kubernetes.io/ja/docs/concepts/extend-kubernetes/operator/)を利用したOracle Database Operator for Kubernetes(通称「OraOperator」)をリリースしました。
現時点(2021/2時点)では、まだ**α版かつ商用利用不可**ですが、今実装されている機能をいくつか紹介していきたいと思います。

なお、OraOperatorについては、昨年の[Oracle Database World](https://www.youtube.com/watch?v=yGCcH29Vuec&ab_channel=Oracle)でも紹介されているので、併せてご覧ください。

# OraOperatorとは

[OraOperator](https://github.com/oracle/oracle-database-operator)はOracleが開発したk8sネイティブにOracle Databaseを動作させたり、管理するためのKubernetes Operatorです。
現時点で、OraOperatorは以下の3環境のみサポートしています.

* [Oracle Container Engine for Kubernetes(以降、OKE)](https://www.oracle.com/jp/cloud-native/container-engine-kubernetes/) v1.17以降
* [Oracle Cloud Native Environment](https://docs.oracle.com/cd/F33069_01/index.html) v1.3以降
* Minikube v1.21以降

また、今の時点で実装されている機能は以下の通りです。

* [Autonomous Database](https://www.oracle.com/autonomous-database/)のSharedモデルに対する以下の操作
    * プロビジョニング
    * 既存Autonomous Databaseのバインド(OraOperatorへの紐付け)
    * 起動/停止
    * 削除
    * スケール
* シングルインスタンスのOracle Database（コンテナ）に対する以下の操作
    * プロビジョニング
    * クローン
    * パッチ
    * データベース初期化パラメータのアップデート
    * データベース設定のアップデート
    * Oracle Enterprise Manager（EM）Express
* Oracle Database（コンテナ）のシャーディング構成
    * シャードデータベースとトポロジのデプロイ
    * 新しいシャードの追加
    * 既存シャードの削除

~~Autonomous Databaseに対する操作をこの記事で取り上げても面白くないので~~、この記事ではOracle Database（コンテナ）のシャーディング構成を簡単に試してみたいと思います。
ちなみに、シングルインスタンスのOracle Database（コンテナ）の操作については、簡単ですが、[こちらのイベント](https://ochacafe.connpass.com/event/232810/)で取り上げているので、よければご覧ください。
[動画](https://www.youtube.com/watch?v=kB8aof8Idg4&t=2421s&ab_channel=JapanOracleDevelopers)と[セッション資料](https://speakerdeck.com/oracle4engineer/kubernetes-operator-introduction)はこちらにあります。

# OraOperatorを利用したシャーディング構成

今回は動作環境として、OKE v1.21を利用していきたいと思います。

## 環境構築

OKEの初期構築については[こちら](https://oracle-japan.github.io/ocitutorials/cloud-native/oke-for-commons/)をご確認ください。

## OraOperatorのインストール

まずは、OraOperatorをインストールするための事前作業を行なっていきます。

OraOperatorは、ユーザからのパラメータを検証するためにWebhookを利用しますが、そこで必要となるTLS証明書を生成するために`cert-manager`をインストールします。

```sh
kubectl apply -f https://github.com/jetstack/cert-manager/releases/latest/download/cert-manager.yaml
```

次に、OraOperatorのレポジトリをcloneしておきます。

```sh
git clone https://github.com/oracle/oracle-database-operator.git
```

レポジトリの中にある`oracle-database-operator.yaml`をapplyします。
このManifestの中にOraOperatorに必要なCustomResourceDefinitionなどが含まれています。

```sh
kubectl apply -f oracle-database-operator.yaml
```

これでOraOperatorのインストールは完了です。

## Oracle DatabaseとGlobal Data Service(GDS)のコンテナイメージの準備

ここでは、シャーディング構成を構築するにあたってのOracle DatabaseおよびGlobal Data Service(GDS)のコンテナイメージを準備します。
Global Data Service(GDS)については[こちら](https://docs.oracle.com/cd/E82638_01/jjucp/global-data-services.html)をご確認ください。


この2つのコンテナイメージは[Oracle Container Registry](https://container-registry.oracle.com/ords/f?p=113:10::::::)の`Database Repositories`に格納されています。
利用するイメージは`gsm`と`enterprise`です。
このコンテナイメージの取得にはOracle シングルサインオンアカウントが必要です。

今回は、Oracle Container Registryに格納されているコンテナイメージを使っていきたいと思います。

Oracle Container Registryでは、事前に利用規約および制限事項に同意して頂く必要があるので、Webサイトの方から同意してください。

また、独自のコンテナイメージを構築することもでき、それぞれ以下のレポジトリよりビルドすることができます。

* [Global Data Service Image](https://github.com/oracle/db-sharding/tree/master/docker-based-sharding-deployment/dockerfiles)
* [Oracle Database Image](https://github.com/oracle/docker-images/tree/main/OracleDatabase/SingleInstance)

## シャーディング構成の構築

ここからは、実際にOracle Databaseのシャーディング構成を構築していきます。

まずは、シャーディング構成を構築する環境のネームスペースを作成します。
今回は、`shns`という名前で作成します。

```sh
kubectl create ns shns
```

次に、Oracle Container RegistryからコンテナイメージをpullできるようにするためのSecretを作成しておきます。

`<your-sso-username>`、`<your-sso-password>`はご自身のアカウントに置き換えてください。
`--docker-email`で指定するメールアドレスは適当で構いません。

```sh
kubectl create secret docker-registry ocr-reg-cred --docker-username=<your-sso-username> \
  --docker-server=https://container-registry.oracle.com --docker-password=<your-sso-password> \
  --docker-email=xxxx.xxxx@oracle.com -n shns
```


次に、後ほどデータベースに外部接続できるようにするために、Manifestを少し編集しておきます。

ちなみに、今回は以下のシャーディング構成を構築します。

* シャードデータベース1
* シャードデータベース2
* Global Service Manager(GSM)1
* Global Service Manager(GSM)2
* カタログデータベース

```sh
vim docs/sharding/provisioning/shard_prov.yaml
```

`isExternalSvc`というフィールドを`True`に設定します。
これで、シャーディング構成が構築された場合にそのデータベースに外部接続可能なLoadBalancer(今回はOCI LoadBalancer)がプロビジョニングされます。

```yaml
#
# Copyright (c) 2021, Oracle and/or its affiliates. 
# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl.
#
apiVersion: database.oracle.com/v1alpha1
kind: ShardingDatabase
metadata:
  name: shardingdatabase-sample
  namespace: shns
spec:
 shard:
    - name: shard1
      storageSizeInGb: 50
    - name: shard2
      storageSizeInGb: 50
 catalog:
    - name: catalog
      storageSizeInGb: 50
 gsm:
    - name: gsm1
      storageSizeInGb: 50
      replicas: 1
      envVars:
        - name:  "SERVICE1_PARAMS"
          value: "service_name=oltp_rw_svc;service_role=primary"
        - name:  "SERVICE2_PARAMS"
          value: "service_name=oltp_ro_svc;service_role=primary"
    - name: gsm2
      storageSizeInGb: 50
      replicas: 1
      envVars:
        - name:  "SERVICE1_PARAMS"
          value: "service_name=oltp_rw_svc;service_role=primary"
        - name:  "SERVICE2_PARAMS"
          value: "service_name=oltp_ro_svc;service_role=primary"
 storageClass: oci
 dbImage: container-registry.oracle.com/database/enterprise:latest
 dbImagePullSecret: ocr-reg-cred
 gsmImage: container-registry.oracle.com/database/gsm:latest
 gsmImagePullSecret: ocr-reg-cred
 scriptsLocation: "set -ex;curl https://codeload.github.com/oracle/db-sharding/tar.gz/master |   tar -xz --strip=4 db-sharding-master/docker-based-sharding-deployment/dockerfiles/21.3.0/scripts; cp -i -r scripts/* /opt/oracle/scripts/sharding/scripts/;cp -i -r scripts/*py /opt/oracle/scripts/sharding"
 secret: db-user-pass
 isExternalSvc: false  <= #ここを"True"に
 isDeleteOraPvc: True
 namespace: shns
```

次に、シャーディング構成で構築するデータベースのデータベースパスワードをSecretとして作成します。
今回は、パスワードを`ORacle_21c`とします。

```sh
# 一時ディレクトリ作成
mkdir /tmp/.secrets/
# ランダムOpenssl keyの作成
openssl rand -hex 64 -out /tmp/.secrets/pwd.key
# 必要なパスワードを使用して、テキストファイルに追加
echo ORacle_21c > /tmp/.secrets/common_os_pwdfile
# 上記で生成されたランダムなopensslキーを使用して、ファイルを暗号化
openssl enc -aes-256-cbc -md md5 -salt -in /tmp/.secrets/common_os_pwdfile -out /tmp/.secrets/common_os_pwdfile.enc -pass file:/tmp/.secrets/pwd.key
# セキュリティのため、パスワードテキストファイルの削除
rm -f /tmp/.secrets/common_os_pwdfile
# Secretの作成
kubectl create secret generic db-user-pass --from-file=/tmp/.secrets/common_os_pwdfile.enc --from-file=/tmp/.secrets/pwd.key -n shns
# Secretの確認
kubectl get secret -n shns
```

:::note warn
**opensslについて**
上記手順で利用する`openssl`コマンドですが、バージョンやFIPSモード有無によって、この後作成するPodが起動しない可能性があります。
私の環境では、以下のバージョンで試しているので、取り急ぎ試したい方はご参考にしてください。
```sh
$ openssl version
OpenSSL 1.1.1g FIPS  21 Apr 2020
```
:::

次に、実際にシャーディング構成を構築するManifestをデプロイしていきます。

```sh
kubectl apply -f docs/sharding/provisioning/shard_prov.yaml
```

これでシャーディング構成が自動的に構築されます。

以下のコマンドを実行し、すべてのPodが起動するまで少し待機します。

```sh
kubectl get pods -n shns -w
```

以下のようになれば起動完了です。
コンテナイメージがかなり重いので、初回起動時はすべてのPodが`Running`になるまで少し時間がかかります。(目安は10〜15分程度です)

```sh
NAME        READY   STATUS    RESTARTS   AGE
catalog-0   1/1     Running   0          10m
gsm1-0      1/1     Running   0          10m
gsm2-0      1/1     Running   0          10m
shard1-0    1/1     Running   0          10m
shard2-0    1/1     Running   0          10m
```

## データベースへの接続

最後に構築したシャーディング構成に接続してみます。
まずは、Serviceを確認してみます。
以下のように`EXTERNAL-IP`が付与されていることが確認できます。
ちなみに[Headless Service](https://kubernetes.io/ja/docs/concepts/services-networking/service/#headless-service)も存在しています。
これは、OCI LoadBalancerのグローバルIPです。

```sh
$ kubectl get svc -n shns
NAME           TYPE           CLUSTER-IP      EXTERNAL-IP       PORT(S)                                                       AGE
catalog        ClusterIP      None            <none>            1521/TCP,6234/TCP,6123/TCP,8080/TCP                           75m
catalog0-svc   LoadBalancer   10.96.205.130   129.158.xxx.xxx    1521:30418/TCP,6234:30705/TCP,6123:30797/TCP,8080:30359/TCP   74m
gsm1           ClusterIP      None            <none>            1522/TCP,6234/TCP,6123/TCP,8080/TCP                           75m
gsm10-svc      LoadBalancer   10.96.110.201   129.153.xxx.xxx   1522:30963/TCP,6234:32176/TCP,6123:31227/TCP,8080:30845/TCP   73m
gsm2           ClusterIP      None            <none>            1522/TCP,6234/TCP,6123/TCP,8080/TCP                           75m
gsm20-svc      LoadBalancer   10.96.206.156   152.xxx.xxx.xxx    1522:30813/TCP,6234:31681/TCP,6123:32605/TCP,8080:31177/TCP   72m
shard1         ClusterIP      None            <none>            1521/TCP,6234/TCP,6123/TCP,8080/TCP                           75m
shard10-svc    LoadBalancer   10.96.20.133    129.159.xxx.xxx   1521:30976/TCP,6234:30188/TCP,6123:31735/TCP,8080:31039/TCP   75m
shard2         ClusterIP      None            <none>            1521/TCP,6234/TCP,6123/TCP,8080/TCP                           75m
shard20-svc    LoadBalancer   10.96.51.140    129.153.xxx.xxx   1521:30293/TCP,6234:30964/TCP,6123:32069/TCP,8080:32229/TCP   74m
```

今回はお試しでシャード・カタログのデータベース(`catalogpdb`)に接続してみます。

```sh
$ sql sys/ORacle_21c@//129.158.xxx.xxx:1521/catalogpdb as sysdba

SQLcl: Release 20.3 Production on Wed Feb 09 08:31:27 2022

Copyright (c) 1982, 2022, Oracle.  All rights reserved.

Connected to:
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0


SQL> select * from dual;

   DUMMY 
________ 
X        

SQL> 
```

無事接続できました。
本来であれば、データベースにLoadBalancer経由で接続することはないと思いますが、現状のOraOperatorだとこの接続方法とHeadless Serviceを利用した２通りの接続方法しかないように見えるので、まだα版ということもありご勘弁を...

他のデータベースに接続する方法は[こちら](https://github.com/oracle/oracle-database-operator/blob/main/docs/sharding/provisioning/database_connection.md)に記載されているので、遊んでみてください！

# まとめ
試してきたように、レガシーのイメージが強いOracle Databaseでも、Kubernetes Operatorを利用してk8sネイティブに動作させるような取り組みが始まっています。
今後もいろんな機能や操作対象が増えてくると思うので、期待ですね！
今回試したシャーディング構成以外にも、シングルインスタンス構成含めて様々なユースケースのManifestがレポジトリに配置されているので、興味がある方はぜひお試しください！

またOraOperatorでアップデートがありましたら、記事にしようかと思います。

# 参考記事

https://github.com/oracle/oracle-database-operator