Astra Control Python SDKを使ってコンテナアプリのバックアップを自動化してみた

# 本記事の要約
- (前提)**Astra Control**はNetAppが提供するKubernetesアプリケーションのバックアップソフトウェアならびにサービスです
- 今回はNetAppがコミュニティ上で提供している[**NetApp Astra Control Python SDK**](https://github.com/NetApp/netapp-astra-toolkits)を使って、PythonスクリプトからAstra Controlの各種操作を行ってみました
- このSDKを利用することでCI/CDパイプラインにアプリケーションのバックアップ処理などを容易に組み込むことができるため、開発スピードを損なわずにアプリケーションのデータ保護を実現することができると思います。

:::note warn
本記事はAstra Controlの応用的な活用法について解説しています。
[Astra Controlの基礎的な記事](https://qiita.com/yshimizu22/items/11967a351e3b1ddde00f)を以前書きましたので良ければご覧ください。
:::

# NetApp Astra Control Python SDKについて
Astra Controlでは基本的にGUIとREST APIの2つのインタフェースを用いて、Kubernetesアプリケーションのバックアップや移行などの操作を行うことができます。
一方で今回の本題である**NetApp Astra Control Python SDK**は、Astra ControlのREST APIを抽象化したSDKであり、Pythonコードを用いた日々のオペレーションやサードパーティサービスとの統合などが更に簡単にできるという点がコンセプトです。

なお、ツールの提供やメンテナンスはコミュニティベース(GitHub)で行われています。

https://github.com/NetApp/netapp-astra-toolkits

# 基本的な使い方
## Python SDKの初回セットアップ
こちらはSDKを利用するクライアントごとに一度だけ実施する設定です。
まずはネイティブのREST APIを使う場合と同様に、Astra Control上でAPIアクセスキーを発行します。

Astra Controlにログインし、右上アイコンから"API access"をクリック
<img width="400px" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/436161/7e2b6c9c-e8bb-226b-5475-5af90fea4abd.png">

画面上部のAccount IDを控えておきつつ、**Generate new API token**をクリック
![スクリーンショット 2023-06-29 14.18.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/436161/82492542-eebd-0cd5-eae3-0e40b33434c3.png)

Generate API tokenをクリックして表示されるトークンも控えておきます。
![スクリーンショット 2023-06-29 14.16.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/436161/48cbb3d1-b470-61ea-d7ab-feb668680f4e.png)



次に本SDKを使用するサーバ等にログインし、Pythonライブラリをインストールします。

```bash
# astra control toolkitのインストール
python3 -m pip install actoolkit
```

その後ツールを使用するための初回セットアップを行います。
本SDKではカレントディレクトリまたは、特定のディレクトリ配下(```~/.config/astra-toolkits``` or ```/etc/astra-toolkits/```)にある```config.yaml```というファイルからAPIアクセスキーなどの情報を読み取る仕様になっています。
今回は```~/.config/astra-toolkits```配下に```config.yaml```を作成します。

```bash
$ mkdir -p ~/.config/astra-toolkits
$ vi ~/.config/astra-toolkits/config.yaml
headers:
  Authorization: Bearer thisIsJustAnExample_token-replaceWithYours== # "Bearer"に続いて、上記で発行したアクセストークンを指定
uid: 12345678-abcd-4efg-1234-567890abcdef # 上記で確認したアカウントIDを指定
astra_project: astra.ps-lab.local # Astra ControlのFQDN
verifySSL: False 
# 上記FQDNに対する証明書検証の有無
# 今回はAstra Control Center(オンプレ版Astra Control)かつ自己署名証明書を使用している環境のためFalseを指定
```

これで初回セットアップは完了です。

## Python SDKの利用
簡単な動作確認としてAstra Control上で管理されているKubernetesクラスタのリストを表示してみます。なお本SDKは以下の様に2通りの使用方法があります。

```bash
# 使用方法その①: コマンドラインから使用
$ actoolkit list clusters
+---------------+--------------------------------------+---------------+------------+---------+----------------+-----------------------+
| clusterName   | clusterID                            | clusterType   | location   | state   | managedState   | tridentStateAllowed   |
+===============+======================================+===============+============+=========+================+=======================+
| cluster.local | 9ced6959-906f-4c5a-bbfb-12af92c2c7d5 | kubernetes    |            | running | managed        | unmanaged             |
+---------------+--------------------------------------+---------------+------------+---------+----------------+-----------------------+

# 使用方法その②: Pythonライブラリとしてインポートして使用
$ python3
Python 3.10.6 (main, May 29 2023, 11:10:38) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import astraSDK
>>> print(astraSDK.clusters.getClusters(output="table").main())
+---------------+--------------------------------------+---------------+------------+---------+----------------+-----------------------+
| clusterName   | clusterID                            | clusterType   | location   | state   | managedState   | tridentStateAllowed   |
+===============+======================================+===============+============+=========+================+=======================+
| cluster.local | 9ced6959-906f-4c5a-bbfb-12af92c2c7d5 | kubernetes    |            | running | managed        | unmanaged             |
+---------------+--------------------------------------+---------------+------------+---------+----------------+-----------------------+
```

本SDKで呼び出すことができるクラスの一覧や、コマンドラインの使い方は以下ドキュメントをご覧ください。
https://github.com/NetApp/netapp-astra-toolkits/tree/main/docs


# もうちょっと実践的な活用例を考えてみる
上記の例だけだと味気ないので、SDKを使用した実践的なインテグレーションの例として、以下の様なサンプルスクリプトを書いてみました。スクリプト実行時、Astra Controlで既に管理されているアプリケーションの名称を引数に指定してあげることで、対象アプリケーションのバックアップを取得してくれるスクリプトです。

```python
# !pip install actoolkit
import astraSDK
import sys
import time
import datetime

# Astra Control上のアプリケーション名からアプリケーションIDを特定する関数
def get_appid_by_appname(app_name):
    print(f'INFO: Finding the application "{args[1]}" in Astra Control...')

    # Astra Controlで管理されているアプリケーション一覧から引数で与えられた名称を持つアプリケーションを検索
    app_info = astraSDK.apps.getApps().main(nameFilter=app_name)
    try:
        # 該当のアプリケーションが存在した場合はアプリケーションIDを返却
        app_id = app_info["items"][0]["id"]
        print(f'INFO: The application "{args[1]}" is successfully found in Astra Control')
        return app_id
    except: 
        print(f'ERROR: The application "{args[1]}" is not found in Astra Control')
        return None

# Astra Control上でバックアップを実行する関数
# is_wait_for_backup_completion=Trueの場合、バックアップジョブの完了まで待機する
def take_backup(app_name:str, app_id:str, will_wait_for_backup_completion:bool=True):
    print(f'INFO: Taking backup for the application "{args[1]}"...')

    # 作成するバックアップの名称を生成(<APP_NAME>-backup-<YYYYMMDDhhmmss>)
    t_delta = datetime.timedelta(hours=9)
    JST = datetime.timezone(t_delta, 'JST')
    now = datetime.datetime.now(JST)
    timestamp = now.strftime('%Y%m%d%H%M%S')
    backup_name = app_name + "-backup-" + timestamp

    # バックアップジョブを実行
    try:
        backup_id = astraSDK.backups.takeBackup().main(appID=app_id, backupName=backup_name)
        print(f'INFO: Backup job for "{backup_name}" is successfully initiated')
    except Exception as e:
        print(f'ERROR: Failed to initiate the backup job for "{backup_name}"')
        print(e)
        return "failed"

    # バックアップジョブの完了を待機
    if will_wait_for_backup_completion:
        # 30秒ごとに10回チェック（最大5分バックアップジョブの完了を待機）
        CHECK_INTERVAL_IN_SECONDS = 30
        RETRY_COUNT = 10

        while RETRY_COUNT > 0:
            print(f'INFO: Checking status of the backup job "{backup_id}"...')

            # バックアップジョブの状態を取得
            backup_jobs = astraSDK.backups.getBackups().main(appFilter=app_id)
            for backup_job in backup_jobs["items"]:
                if backup_job["id"] == backup_id:
                    backup_status = backup_job["state"]

            # バックアップジョブが正常終了した場合
            if backup_status == "completed": 
                print(f'INFO: The backup job "{backup_id}" is successfully completed!')
                return backup_status
            # バックアップジョブが異常終了した場合
            elif backup_status == "failed": 
                print(f'ERROR: The backup job "{backup_id}" is failed. Check the Astra Control.')
                return backup_status
            # バックアップジョブがキャンセルされた場合
            elif backup_status == "canceled": 
                print(f'WARNING: The backup job "{backup_id}" is canceled')
                return backup_status

            # バックアップジョブが実行中の場合
            elif backup_status == "pending" or backup_status == "running":
                print(f'INFO: The backup job "{backup_id}" is still in progress')

                RETRY_COUNT -= 1
                if RETRY_COUNT > 0:
                    print(f"INFO: Wating for next check...({RETRY_COUNT} retries remaining)")
                    time.sleep(CHECK_INTERVAL_IN_SECONDS)
                continue
            # その他のステータスの場合は例外として処理
            else:
                print(f'ERROR: The backup job "{backup_id}" has invalid status. Check the Astra Control.')
                return "unknown"
        print(f"WARNING: Reached the max retries. To get the latest status of the backup job, check the Astra Control.")
        return "timeouted"
    else: 
        return "initiated"

# Usage: python3 astra_backup.py <APP_NAME>
if __name__ == '__main__':
    args = sys.argv
    # スクリプト実行時の引数をチェック
    if len(args) == 2:
        # アプリケーション名からアプリケーションIDを取得
        app_id = get_appid_by_appname(args[1])
        # アプリケーションIDが取得できた場合、バックアップを実行
        if app_id:
            ret = take_backup(app_name=args[1], app_id=app_id)
    else:
        print("Usage: python3 astra_backup.py <APP_NAME>")
```

実際に手元で動かしてみた。
余談ですが今回バックアップ対象としているのはKubernetes上にデプロイしたコンテナレジストリ(harbor)です。

```bash
$ python3 astra_backup.py harbor
INFO: Finding the application "harbor" in Astra Control...
INFO: The application "harbor" is successfully found in Astra Control
INFO: Taking backup for the application "harbor"...
INFO: Backup job for "harbor-backup-20230630140637" is successfully initiated
INFO: Checking status of the backup job "b53b2097-ad12-402f-87c0-0ccf8981ef93"...
INFO: The backup job "b53b2097-ad12-402f-87c0-0ccf8981ef93" is still in progress
INFO: Wating for next check...(9 retries remaining)
INFO: Checking status of the backup job "b53b2097-ad12-402f-87c0-0ccf8981ef93"...
INFO: The backup job "b53b2097-ad12-402f-87c0-0ccf8981ef93" is still in progress
INFO: Wating for next check...(8 retries remaining)
INFO: Checking status of the backup job "b53b2097-ad12-402f-87c0-0ccf8981ef93"...
INFO: The backup job "b53b2097-ad12-402f-87c0-0ccf8981ef93" is still in progress
INFO: Wating for next check...(7 retries remaining)
INFO: Checking status of the backup job "b53b2097-ad12-402f-87c0-0ccf8981ef93"...
INFO: The backup job "b53b2097-ad12-402f-87c0-0ccf8981ef93" is still in progress
INFO: Wating for next check...(6 retries remaining)
INFO: Checking status of the backup job "b53b2097-ad12-402f-87c0-0ccf8981ef93"...
INFO: The backup job "b53b2097-ad12-402f-87c0-0ccf8981ef93" is successfully completed!
```

念の為、Astra Control上でのGUI上でもバックアップが取れているか確認（取れてました）
![スクリーンショット 2023-06-30 18.19.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/436161/6606894e-df71-201a-4a6d-451b9ce3af2f.png)

なおAstra Controlには基本機能で保護ポリシーという機能があり、日次や週次といった定期的なバックアップ運用に関してはわざわざ本SDKを使用せずとも(スクリプトを書かずとも)自動化できます。
従って実際に上記の様なスクリプトを使う場面としては、「主にサードパーティのプラットフォームにAstra Controlの機能を統合したい場合」ということになるでしょう。

例えばJenkinsやGitHub ActionsなどのCI/CDプラットフォームと上記のようなスクリプトを統合することで、アプリのビルドやテストなど(=CI)の完了後、環境へのデプロイ(=CD)が走る前に、自動で現在デプロイされているアプリの状態をバックアップしておく、なんてことが可能になります。
**つまりCI/CDパイプラインを活かした高速な開発サイクルを損なうことなく、コンテナアプリケーションの保護がバッチリできてしまうということです。**
万が一アプリケーションのデータ周りのバグを潰しきれないままデプロイしてしまって、アプリのデータが壊れてしまった！なんて際にもこれなら安心ですね。
![dwn_image1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/436161/37d66612-397b-5253-4f9b-7562c8b5abe9.jpeg)

このような具体的なユースケースはNetAppのドキュメントにもいくつか掲載されておりますので、ぜひこちらも併せてご覧ください。
https://docs.netapp.com/ja-jp/netapp-solutions/containers/devops_with_netapp/dwn_use_cases_overview.html