1. 概要
このラボでは、コンテナ化された環境で Python アプリケーションを開発するソフトウェア エンジニア向けの開発ワークフローを効率化するように設計された機能と機能について説明します。一般的なコンテナ開発では、コンテナの詳細とコンテナのビルドプロセスを理解する必要があります。また、デベロッパーは通常、IDE から離れてリモート環境でアプリケーションをテストしてデバッグする必要があります。このチュートリアルで説明するツールとテクノロジーを使用すると、デベロッパーは IDE を離れることなく、コンテナ化されたアプリケーションを効率的に操作できます。
学習内容
このラボでは、GCP でコンテナを使用して開発する方法について学習します。
- 新しい Python スターター アプリケーションを作成する
- 開発プロセスを理解する
- 簡単な CRUD REST サービスを開発する
2. 設定と要件
セルフペース型の環境設定
- Google Cloud Console にログインして、プロジェクトを新規作成するか、既存のプロジェクトを再利用します。Gmail アカウントも Google Workspace アカウントもまだお持ちでない場合は、アカウントを作成してください。



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

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

- エディタが開き、右側にエクスプローラ、中央にエディタが表示されます。
- 画面の下部にもターミナル ペインが表示されます。
- ターミナルが開いていない場合は、`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)')
ソースコードを取得する
- このラボのソースコードは、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 スターター アプリケーションを作成する
requirements.txtという名前のファイルを作成し、次の内容をコピーします。
Flask
gunicorn
google-cloud-spanner
ptvsd==4.3.2
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')
- 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 を生成します。
- 次のコマンドで Skaffold を初期化します。
skaffold init --generate-manifests
プロンプトが表示されたら、矢印キーでカーソルを移動し、Space キーでオプションを選択します。
次のいずれかを選択:
- ポートの場合は
8080 - 構成を保存する場合は
y
Skaffold 構成を更新する
- デフォルトのアプリケーション名を変更する
skaffold.yamlを開く- 現在
dockerfile-imageに設定されているイメージ名を選択する - 右クリックして [すべての出現箇所を変更] を選択する
- 新しい名前として
python-appと入力する - ビルド セクションをさらに編集して
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 構成ファイルを変更する
- デフォルトの名前を変更する
deployment.yamlファイルを開く- 現在
dockerfile-imageに設定されているイメージ名を選択する - 右クリックして [すべての出現箇所を変更] を選択する
- 新しい名前として
python-appと入力する
4. 開発プロセスを理解する
ビジネス ロジックを追加したので、アプリケーションをデプロイしてテストできます。次のセクションでは、Cloud Code プラグインの使用方法について説明します。このプラグインは、Skaffold と統合して開発プロセスを効率化します。次の手順で GKE にデプロイすると、Cloud Code と Skaffold はコンテナ イメージを自動的にビルドして Container Registry に push し、アプリケーションを GKE にデプロイします。これは舞台裏で行われ、デベロッパーのフローから詳細が抽象化されます。
Kubernetes へのデプロイ
- Cloud Shell エディタの下部にあるペインで、Cloud Code  を選択します。

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

このコマンドは、ソースコードのビルドを開始し、テストを実行します。ビルドとテストの実行には数分かかります。これらのテストには、単体テストと、デプロイ環境に設定されたルールをチェックする検証ステップが含まれています。この検証ステップはすでに構成されており、開発環境で作業している場合でも、デプロイの問題に関する警告が表示されます。
- このコマンドを初めて実行すると、画面の上部にプロンプトが表示され、現在の Kubernetes コンテキストを使用するかどうかを確認されます。[はい] を選択して、現在のコンテキストを受け入れて使用します。
- 次に、使用するコンテナ レジストリを選択するよう求めるプロンプトが表示されます。Enter キーを押して、提供されたデフォルト値を受け入れます。
- 下部ペインの [出力] タブを選択して、進行状況と通知を表示します。

- 右側のチャンネル プルダウンで [Kubernetes: 実行/デバッグ - 詳細] を選択すると、コンテナからライブ ストリーミングされる追加の詳細とログが表示されます。

ビルドとテストが完了すると、[出力] タブに「Attached debugger to container "python-app-8476f4bbc-h6dsl" successfully.」と表示され、URL http://localhost:8080 が表示されます。
- Cloud Code ターミナルで、出力の最初の URL(http://localhost:8080)にカーソルを合わせ、表示されたツールチップで [Open Web Preview] を選択します。
- 新しいブラウザタブが開き、メッセージ
Hello, World!が表示されます。
ホットリロード
app.pyファイルを開きます。- 応答メッセージを
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 ...
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
- ブラウザを更新して、更新された結果を表示します。
デバッグ
- デバッグビューに移動して、現在のスレッド
を停止します。 - 下部メニューの
Cloud Codeをクリックし、Debug on Kubernetesを選択して、アプリケーションをdebugモードで実行します。
Kubernetes Run/Debug - DetailedビューのOutputウィンドウで、Skaffold がこのアプリケーションをデバッグモードでデプロイすることを確認します。
- これを初めて実行すると、コンテナ内のソースの場所を確認するプロンプトが表示されます。この値は、Dockerfile のディレクトリに関連しています。
Enter キーを押してデフォルトを受け入れます。

アプリケーションのビルドとデプロイには数分かかります。
- プロセスが完了すると、デバッガが接続されていることがわかります。
Port forwarding pod/python-app-8bd64cf8b-cskfl in namespace default, remote port 5678 -> http://127.0.0.1:5678
- 下部のステータスバーの色が青からオレンジに変わり、デバッグモードであることを示します。
Kubernetes Run/Debugビューで、デバッグ可能なコンテナが起動していることを確認します。
**************URLs***************** Forwarded URL from service python-app: http://localhost:8080 Debuggable container started pod/python-app-8bd64cf8b-cskfl:python-app (default) Update succeeded ***********************************
ブレークポイントを使用する
app.pyファイルを開きます。return messageと書かれたステートメントを見つけます。- 行番号の左側の空白をクリックして、その行にブレークポイントを追加します。赤いインジケーターが表示され、ブレークポイントが設定されていることがわかります。
- ブラウザを再読み込みすると、デバッガがブレークポイントでプロセスを停止し、GKE でリモートで実行されているアプリケーションの変数と状態を調べることができます。
- [変数] セクションをクリックします。
- [ローカル] をクリックすると、
"message"変数が見つかります。 - 変数名「message」をダブルクリックし、ポップアップで値を別の値(
"Greetings from Python"など)に変更します。 - デバッグ コントロール パネルの [続行] ボタン
をクリックします。 - ブラウザでレスポンスを確認します。入力した値が更新されています。
- 停止ボタン
を押して [デバッグ] モードを停止し、ブレークポイントをもう一度クリックしてブレークポイントを削除します。
5. 簡単な CRUD REST サービスを開発する
この時点で、アプリケーションはコンテナ化された開発用に完全に構成されており、Cloud Code を使用した基本的な開発ワークフローを理解しました。次のセクションでは、Google Cloud のマネージド データベースに接続する REST サービス エンドポイントを追加して、学習した内容を実践します。
REST サービスをコーディングする
次のコードは、アプリケーションのバックエンド データベースとして Spanner を使用するシンプルな REST サービスを作成します。次のコードをアプリケーションにコピーして、アプリケーションを作成します。
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 に安全に接続するには、Workload Identity を使用するようにアプリケーションを設定します。これにより、アプリケーションは独自のサービス アカウントとして動作し、データベースにアクセスするときに個別の権限を持つことができます。
deployment.yamlを更新します。ファイルの末尾に次のコードを追加します(次の例ではタブのインデントを保持してください)。
serviceAccountName: python-ksa
nodeSelector:
iam.gke.io/gke-metadata-server-enabled: "true"
アプリケーションをデプロイして検証する
- Cloud Shell エディタの下部にあるペインで、
Cloud Codeを選択し、画面上部のDebug on Kubernetesを選択します。 - ビルドとテストが完了すると、[出力] タブに「
Resource deployment/python-app status completed successfully」と表示され、URL「Forwarded URL from service python-app: http://localhost:8080」が表示されます。 - エントリをいくつか追加します。
Cloud Shell ターミナルから、次のコマンドを実行します。
curl -X POST http://localhost:8080/singer -H 'Content-Type: application/json' -d '{"first_name":"Cat","last_name":"Meow", "singer_id": 6}'
- ターミナルで次のコマンドを実行して、GET をテストします。
curl -X GET http://localhost:8080/singer?singer_id=6
- 削除をテストする: 次のコマンドを実行して、エントリを削除してみます。必要に応じて、item-id の値を変更します。
curl -X DELETE http://localhost:8080/singer?singer_id=6
This throws an error message
500 Internal Server Error
問題を特定して修正する
- デバッグモードで問題を特定します。次のヒントを参考にしてください。
- DELETE が目的の結果を返さないため、DELETE に問題があることがわかります。
delete_singerメソッドのapp.pyにブレークポイントを設定します。 - ステップ実行を行い、各ステップの変数を監視して、左側のウィンドウでローカル変数の値を確認します。
singer_idやrequest.argsなどの特定の値を監視するには、これらの変数を [ウォッチ] ウィンドウに追加します。
singer_idに割り当てられた値がNoneであることに注意してください。コードを変更して問題を修正します。
修正されたコード スニペットは次のようになります。
@app.route('/delete-singer', methods=['DELETE', 'GET'])
def delete_singer():
try:
singer_id = request.args.get('singer_id')
- アプリケーションが再起動したら、削除を試してもう一度テストします。
- デバッグ ツールバーの赤い四角
をクリックして、デバッグ セッションを停止します。
6. クリーンアップ
おめでとうございます!このラボでは、新しい Python アプリケーションをゼロから作成し、コンテナと効果的に連携するように構成しました。次に、従来のアプリケーション スタックと同じデベロッパー フローに従って、アプリケーションをリモート GKE クラスタにデプロイしてデバッグしました。
ラボを完了したら、次の手順でクリーンアップします。
- ラボで使用したファイルを削除する
cd ~ && rm -rf container-developer-workshop
- プロジェクトを削除して、関連するインフラストラクチャとリソースをすべて削除する