Python によるインナーループ開発

1. 概要

このラボでは、コンテナ化された環境で Python アプリケーションを開発するソフトウェア エンジニア向けに、開発ワークフローを効率化するための特長と機能を紹介します。一般的なコンテナ開発では、ユーザーがコンテナの詳細とコンテナのビルドプロセスを理解する必要があります。さらに、デベロッパーは通常、作業の中断を余儀なくされ、IDE から離れてリモート環境でアプリケーションのテストやデバッグを行う必要もあります。このチュートリアルで説明するツールとテクノロジーを使用すると、デベロッパーは IDE を離れることなく、コンテナ化されたアプリケーションを効果的に操作できます。

学習内容

このラボでは、GCP でコンテナを使用して開発するための次のような方法について学びます。

  • 新しい Python スターター アプリケーションを作成する
  • 開発プロセスを確認する
  • シンプルな CRUD REST サービスを開発する

2. 設定と要件

セルフペース型の環境設定

  1. Google Cloud Console にログインして、プロジェクトを新規作成するか、既存のプロジェクトを再利用します。Gmail アカウントも Google Workspace アカウントもまだお持ちでない場合は、アカウントを作成してください。

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • プロジェクト名は、このプロジェクトの参加者に表示される名称です。Google API では使用されない文字列で、いつでも更新できます。
  • プロジェクト ID は、すべての Google Cloud プロジェクトにおいて一意でなければならず、不変です(設定後は変更できません)。Cloud Console により一意の文字列が自動生成されます(通常は内容を意識する必要はありません)。ほとんどの Codelab では、プロジェクト ID を参照する必要があります(通常、プロジェクト ID は「PROJECT_ID」の形式です)。好みの文字列でない場合は、別のランダムな ID を生成するか、独自の ID を試用して利用可能であるかどうかを確認することができます。プロジェクトの作成後、ID は「フリーズ」されます。
  • 3 つ目の値として、一部の API が使用するプロジェクト番号があります。これら 3 つの値について詳しくは、こちらのドキュメントをご覧ください。
  1. 次に、Cloud のリソースや API を使用するために、Cloud Console で課金を有効にする必要があります。この Codelab の操作をすべて行って、費用が生じたとしても、少額です。このチュートリアルを終了した後に課金が発生しないようにリソースをシャットダウンするには、Codelab の最後にある「クリーンアップ」の手順を行います。Google Cloud の新規ユーザーは、300 米ドル分の無料トライアル プログラムをご利用いただけます。

Cloudshell エディタを起動する

このラボは、Google Cloud Shell エディタで使用するように設計、テストされています。エディタにアクセスするには、

  1. https://console.cloud.google.com から Google プロジェクトにアクセスします。
  2. 右上にある Cloud Shell エディタのアイコンをクリックします。

8560cc8d45e8c112.png

  1. ウィンドウの下部に新しいペインが開きます
  2. [エディタを開く] ボタンをクリックします。

9e504cb98a6a8005.png

  1. エディタが開き、右側にエクスプローラ、中央部分にエディタが表示されます
  2. 画面の下部にターミナル ペインも表示されます。
  3. ターミナルが開いていない場合は、`Ctrl+` キーの組み合わせを使用して新しいターミナル ウィンドウを開きます。

環境設定

Cloud Shell で、プロジェクトのプロジェクト ID とプロジェクト番号を設定します。これらを PROJECT_ID 変数と PROJECT_ID 変数として保存します。

export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID \
    --format='value(projectNumber)')

ソースコードを取得する

  1. このラボのソースコードは、GitHub の GoogleCloudPlatform にある container-developer-workshop にあります。以下のコマンドでクローンを作成し、そのディレクトリに移動します。
git clone https://github.com/GoogleCloudPlatform/container-developer-workshop.git &&
cd container-developer-workshop/labs/python
mkdir music-service && cd music-service 
cloudshell workspace .

ターミナルが開いていない場合は、`Ctrl+` キーの組み合わせを使用して新しいターミナル ウィンドウを開きます。

このラボで使用するインフラストラクチャをプロビジョニングする

このラボでは、GKE にコードをデプロイし、Spanner データベースに保存されているデータにアクセスします。以下の設定スクリプトは、このインフラストラクチャを準備します。プロビジョニング プロセスには 10 分以上かかります。設定を処理している間に、次の手順に進むことができます。

../setup.sh

3. 新しい Python スターター アプリケーションを作成する

  1. requirements.txt というファイルを作成し、次の内容をコピーします。
Flask
gunicorn
google-cloud-spanner
ptvsd==4.3.2
  1. app.py という名前のファイルを作成し、次のコードを貼り付けます。
import os
from flask import Flask, request, jsonify
from google.cloud import spanner

app = Flask(__name__)

@app.route("/")
def hello_world():
    message="Hello, World!"
    return message

if __name__ == '__main__':
    server_port = os.environ.get('PORT', '8080')
    app.run(debug=False, port=server_port, host='0.0.0.0')

  1. Dockerfile という名前のファイルを作成し、次のコードを貼り付けます。
FROM python:3.8
ARG FLASK_DEBUG=0
ENV FLASK_DEBUG=$FLASK_DEBUG
ENV FLASK_APP=app.py
WORKDIR /app
COPY requirements.txt .
RUN pip install --trusted-host pypi.python.org -r requirements.txt
COPY . .
ENTRYPOINT ["python3", "-m", "flask", "run", "--port=8080", "--host=0.0.0.0"]

: FLASK_DEBUG=1 を使用すると、コードの変更を Python Flask アプリに自動的に再読み込みできます。この Dockerfile を使用すると、この値をビルド引数として渡すことができます。

マニフェストを生成する

ターミナルで次のコマンドを実行して、デフォルトの skaffold.yaml と deployment.yaml を生成します。

  1. 次のコマンドを使用して Skaffold を初期化する
skaffold init --generate-manifests

プロンプトが表示されたら、矢印キーでカーソルを動かし、Space キーを使用してオプションを選択します。

次のいずれかを選択:

  • ポートに対する 8080
  • y: 構成を保存する

Skaffold 構成を更新する

  • デフォルトのアプリケーション名を変更する
  • skaffold.yaml を開きます。
  • 現在 dockerfile-image として設定されているイメージ名を選択します
  • 右クリックして [すべてのオカレンスを変更] を選択します。
  • 新しい名前として「python-app」と入力します。
  • build セクションをさらに編集し、
  • docker.buildArgs を追加して FLASK_DEBUG=1 を渡す
  • 設定を同期して、IDE から実行中のコンテナに *.py ファイルへの変更を読み込みます

編集後、skaffold.yaml ファイルのビルド セクションは次のようになります。

build:
 artifacts:
 - image: python-app
   docker:
     buildArgs:
       FLASK_DEBUG: 1
     dockerfile: Dockerfile
   sync:
     infer:
     - '**/*.py'

Kubernetes 構成ファイルを変更する

  1. デフォルトの名前を変更する
  • deployment.yaml ファイルを開く
  • 現在 dockerfile-image として設定されているイメージ名を選択します
  • 右クリックして [すべてのオカレンスを変更] を選択します。
  • 新しい名前として「python-app」と入力します。

4. 開発プロセスのチュートリアル

これで、ビジネス ロジックが追加されたので、アプリケーションのデプロイとテストができるようになりました。次のセクションでは、Cloud Code プラグインの使用に焦点を当てます。特に、このプラグインは skaffold と統合され、開発プロセスを効率化できます。次の手順で GKE にデプロイすると、Cloud Code と Skaffold が自動的にコンテナ イメージをビルドして Container Registry に push し、アプリケーションを GKE にデプロイします。この処理はバックグラウンドで行われ、デベロッパー フローでは詳細が抽象化されます。

Kubernetes へのデプロイ

  1. Cloud Shell エディタの下部にあるペインで、[Cloud Code] を選択します。

fdc797a769040839.png

  1. 上部に表示されるパネルで、[Run on Kubernetes] を選択します。プロンプトが表示されたら、[はい] を選択して現在の Kubernetes コンテキストを使用します。

cfce0d11ef307087.png

このコマンドは、ソースコードのビルドを開始し、テストを実行します。ビルドとテストの実行には数分かかります。これらのテストには、単体テストと、デプロイ環境用に設定されているルールをチェックする検証ステップが含まれます。この検証ステップはすでに構成されているため、開発環境で作業している場合でも、デプロイの問題に関する警告が表示されます。

  1. コマンドを初めて実行すると、画面の上部に現在の Kubernetes コンテキストが必要かどうかを尋ねるプロンプトが表示されます。[はい] を選択します。現在のコンテキストを受け入れて使用します。
  2. 次に、使用する Container Registry を尋ねるプロンプトが表示されます。Enter キーを押して、指定されたデフォルト値を受け入れます。
  3. 下部のペインで [Output] タブを選択すると、進行状況と通知が表示されます。

f95b620569ba96c5.png

  1. [Kubernetes: Run/Debug - Detailed] を選択します。右側のチャネル プルダウンから、追加の詳細情報や、コンテナからライブ ストリーミングされるログを確認できます。

94acdcdda6d2108.png

ビルドとテストが完了すると、[出力] タブに Attached debugger to container "python-app-8476f4bbc-h6dsl" successfully. と表示され、URL http://localhost:8080 が表示されます。

  1. Cloud Code ターミナルで、出力の最初の URL(http://localhost:8080)にカーソルを合わせ、表示されたツールチップで [Open Web Preview] を選択します。
  2. 新しいブラウザタブが開き、「Hello, World!」というメッセージが表示されます

ホットリロード

  1. app.py ファイルを開きます。
  2. 応答メッセージを「Hello from Python」に変更します

すぐに、Output ウィンドウの Kubernetes: Run/Debug ビューで、ウォッチャーが更新されたファイルを Kubernetes のコンテナと同期します。

Update initiated
Build started for artifact python-app
Build completed for artifact python-app

Deploy started
Deploy completed

Status check started
Resource pod/python-app-6f646ffcbb-tn7qd status updated to In Progress
Resource deployment/python-app status updated to In Progress
Resource deployment/python-app status completed successfully
Status check succeeded
...
  1. Kubernetes: Run/Debug - Detailed ビューに切り替えると、ファイルの変更を認識し、アプリをビルドして再デプロイします。
files modified: [app.py]
Syncing 1 files for gcr.io/veer-pylab-01/python-app:3c04f58-dirty@sha256:a42ca7250851c2f2570ff05209f108c5491d13d2b453bb9608c7b4af511109bd
Copying files:map[app.py:[/app/app.py]]togcr.io/veer-pylab-01/python-app:3c04f58-dirty@sha256:a42ca7250851c2f2570ff05209f108c5491d13d2b453bb9608c7b4af511109bd
Watching for changes...
[python-app] * Detected change in '/app/app.py', reloading
[python-app] * Restarting with stat
[python-app] * Debugger is active!
[python-app] * Debugger PIN: 744-729-662
  1. ブラウザを更新して、更新された結果を確認してください。

デバッグ

  1. デバッグビューに移動し、現在のスレッド 647213126d7a4c7b.png を停止します。
  2. 下部のメニューで Cloud Code をクリックし、Debug on Kubernetes を選択して、アプリを debug モードで実行します。
  • Output ウィンドウの Kubernetes Run/Debug - Detailed ビューで、skaffold がこのアプリケーションをデバッグモードでデプロイします。
  1. 初回実行時には、ソースがコンテナ内のどこにあるかを尋ねるプロンプトが表示されます。この値は、Dockerfile 内のディレクトリに関連付けられています。

Enter キーを押してデフォルト値を受け入れます。

583436647752e410.png

アプリケーションのビルドとデプロイには数分かかります。

  1. プロセスが完了したとき。デバッガがアタッチされているのがわかります。
Port forwarding pod/python-app-8bd64cf8b-cskfl in namespace default, remote port 5678 -> http://127.0.0.1:5678
  1. 下部のステータスバーの色が青からオレンジ色に変わり、デバッグモードであることを示します。
  2. Kubernetes Run/Debug ビューで、Debuggable コンテナが起動していることを確認します。
**************URLs*****************
Forwarded URL from service python-app: http://localhost:8080
Debuggable container started pod/python-app-8bd64cf8b-cskfl:python-app (default)
Update succeeded
***********************************

ブレークポイントの活用

  1. app.py ファイルを開きます。
  2. return message」というステートメントを見つけます。
  3. 行番号の左側にある空白スペースをクリックして、その行にブレークポイントを追加します。ブレークポイントが設定されたことを示す赤いインジケーターが表示される
  4. ブラウザを再読み込みすると、デバッガがブレークポイントでプロセスを停止し、GKE でリモートで実行されているアプリケーションの変数と状態を調査できるようになります。
  5. クリックして [変数] セクションまで移動します。
  6. [Locals] をクリックして、"message" 変数を見つけます。
  7. 変数名「message」をダブルクリックします。ポップアップで、値を "Greetings from Python" など別のものに変更します。
  8. デバッグ用コントロール パネルの [続行] ボタンをクリックします。607c33934f8d6b39.png
  9. ブラウザでレスポンスを確認します。入力した値が更新されています。
  10. 「Debug」と表示モードに切り替えるには停止ボタン 647213126d7a4c7b.png を押します。ブレークポイントをもう一度クリックすると、ブレークポイントを削除できます。

5. シンプルな CRUD REST サービスの開発

これで、アプリケーションはコンテナ化された開発用に完全に構成され、Cloud Code での基本的な開発ワークフローはひととおり確認できました。以降のセクションでは、Google Cloud のマネージド データベースに接続する REST サービス エンドポイントを追加して、学習した内容を実践します。

REST サービスをコーディングする

以下のコードでは、アプリケーションの基盤となるデータベースとして Spanner を使用するシンプルな REST サービスを作成します。次のコードをアプリケーションにコピーしてアプリケーションを作成します。

  1. app.py を次の内容に置き換えて、メイン アプリケーションを作成します。
import os
from flask import Flask, request, jsonify
from google.cloud import spanner


app = Flask(__name__)


instance_id = "music-catalog"

database_id = "musicians"

spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)


@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

@app.route('/singer', methods=['POST'])
def create():
    try:
        request_json = request.get_json()
        singer_id = request_json['singer_id']
        first_name = request_json['first_name']
        last_name = request_json['last_name']
        def insert_singers(transaction):
            row_ct = transaction.execute_update(
                f"INSERT Singers (SingerId, FirstName, LastName) VALUES" \
                f"({singer_id}, '{first_name}', '{last_name}')"
            )
            print("{} record(s) inserted.".format(row_ct))

        database.run_in_transaction(insert_singers)

        return {"Success": True}, 200
    except Exception as e:
        return e



@app.route('/singer', methods=['GET'])
def get_singer():

    try:
        singer_id = request.args.get('singer_id')
        def get_singer():
            first_name = ''
            last_name = ''
            with database.snapshot() as snapshot:
                results = snapshot.execute_sql(
                    f"SELECT SingerId, FirstName, LastName FROM Singers " \
                    f"where SingerId = {singer_id}",
                    )
                for row in results:
                    first_name = row[1]
                    last_name = row[2]
                return (first_name,last_name )
        first_name, last_name = get_singer()  
        return {"first_name": first_name, "last_name": last_name }, 200
    except Exception as e:
        return e


@app.route('/singer', methods=['PUT'])
def update_singer_first_name():
    try:
        singer_id = request.args.get('singer_id')
        request_json = request.get_json()
        first_name = request_json['first_name']
        
        def update_singer(transaction):
            row_ct = transaction.execute_update(
                f"UPDATE Singers SET FirstName = '{first_name}' WHERE SingerId = {singer_id}"
            )

            print("{} record(s) updated.".format(row_ct))

        database.run_in_transaction(update_singer)
        return {"Success": True}, 200
    except Exception as e:
        return e


@app.route('/singer', methods=['DELETE'])
def delete_singer():
    try:
        singer_id = request.args.get('singer')
    
        def delete_singer(transaction):
            row_ct = transaction.execute_update(
                f"DELETE FROM Singers WHERE SingerId = {singer_id}"
            )
            print("{} record(s) deleted.".format(row_ct))

        database.run_in_transaction(delete_singer)
        return {"Success": True}, 200
    except Exception as e:
        return e

port = int(os.environ.get('PORT', 8080))
if __name__ == '__main__':
    app.run(threaded=True, host='0.0.0.0', port=port)

データベース構成の追加

Spanner に安全に接続するには、ワークロード ID を使用するようにアプリケーションを設定します。これにより、アプリケーションは独自のサービス アカウントとして機能し、データベースにアクセスする際の個別の権限を持ちます。

  1. deployment.yaml を更新します。ファイルの末尾に次のコードを追加します(以下の例ではタブのインデントを維持してください)。
      serviceAccountName: python-ksa
      nodeSelector:
        iam.gke.io/gke-metadata-server-enabled: "true" 

アプリケーションのデプロイと検証

  1. Cloud Shell エディタの下部にあるペインで Cloud Code を選択し、画面上部の Debug on Kubernetes を選択します。
  2. ビルドとテストが完了すると、[Output] タブに Resource deployment/python-app status completed successfully と表示され、URL として「Forwarded URL from service python-app: http://localhost:8080」が表示されます。
  3. いくつかのエントリを追加します。

cloudshell ターミナルから、以下のコマンドを実行します。

curl -X POST http://localhost:8080/singer -H 'Content-Type: application/json' -d '{"first_name":"Cat","last_name":"Meow", "singer_id": 6}'
  1. ターミナルで以下のコマンドを実行して GET をテストします。
curl -X GET http://localhost:8080/singer?singer_id=6
  1. 削除をテストする: 次のコマンドを実行して、エントリを削除してみます。必要に応じて、item-id の値を変更します。
curl -X DELETE http://localhost:8080/singer?singer_id=6
    This throws an error message
500 Internal Server Error

問題を特定して修正する

  1. デバッグモードで問題を見つけます。次のヒントを参考にしてください。
  • DELETE が意図した結果を返さないため、何か問題があることがわかります。そのため、ブレークポイントは delete_singer メソッドの app.py に設定します。
  • ステップごとに実行し、各ステップの変数を監視して、左側のウィンドウでローカル変数の値を確認します。
  • singer_idrequest.args などの特定の値をモニタリングするには、これらの変数を [Watch] ウィンドウに追加します。
  1. singer_id に割り当てられた値が None であることに注意してください。コードを変更して問題を解決してください。

固定コード スニペットは次のようになります。

@app.route('/delete-singer', methods=['DELETE', 'GET'])
def delete_singer():
    try:
        singer_id = request.args.get('singer_id')
  1. アプリケーションが再起動したら、削除してもう一度テストします。
  2. デバッグ ツールバーの赤い正方形 647213126d7a4c7b.png をクリックして、デバッグ セッションを停止します

6. クリーンアップ

これで、このラボでは、新しい Python アプリケーションをゼロから作成し、コンテナと効果的に連携するように構成しました。その後、従来のアプリケーション スタックで見られるのと同じデベロッパー フローに従って、アプリケーションをリモート GKE クラスタにデプロイし、デバッグしました。

ラボの完了後にクリーンアップするには:

  1. ラボで使用したファイルを削除する
cd ~ && rm -rf container-developer-workshop
  1. プロジェクトを削除して、関連するすべてのインフラストラクチャとリソースを削除する