1. 概要
このラボでは、コンテナ化された環境で Python アプリケーションの開発を担当するソフトウェア エンジニアの開発ワークフローを効率化するために設計された機能について説明します。一般的なコンテナ開発では、コンテナの詳細とコンテナ ビルドプロセスを理解する必要があります。また、デベロッパーは通常、フローを中断して IDE から移動し、リモート環境でアプリケーションをテストしてデバッグする必要があります。このチュートリアルで説明したツールとテクノロジーを使用すると、開発者は IDE を離れることなく、コンテナ化されたアプリケーションを効率的に操作できます。
学習内容
このラボでは、GCP でコンテナを使用して開発する方法について学びます。
- 新しい Python スターター アプリケーションの作成
- 開発プロセスを説明する
- シンプルな CRUD REST サービスを開発する
- GKE へのデプロイ
- エラー状態をデバッグする
- ブレークポイント / ログの活用
- 変更を GKE にホットデプロイする

2. 設定と要件
セルフペース型の環境設定
- Google Cloud Console にログインして、プロジェクトを新規作成するか、既存のプロジェクトを再利用します。Gmail アカウントも Google Workspace アカウントもまだお持ちでない場合は、アカウントを作成してください。



- プロジェクト名は、このプロジェクトの参加者に表示される名称です。Google API では使用されない文字列です。この設定はいつでも変更できます。
- プロジェクト ID は、すべての Google Cloud プロジェクトにおいて一意でなければならず、不変です(設定後は変更できません)。Cloud コンソールでは一意の文字列が自動生成されます。通常は、この内容を意識する必要はありません。ほとんどの Codelab では、プロジェクト ID(通常は
PROJECT_IDと識別されます)を参照する必要があります。生成された ID が好みではない場合は、ランダムに別の ID を生成できます。または、ご自身で試して、利用可能かどうかを確認することもできます。このステップ以降は変更できず、プロジェクトを通して同じ ID になります。 - なお、3 つ目の値として、一部の API が使用するプロジェクト番号があります。これら 3 つの値について詳しくは、こちらのドキュメントをご覧ください。
- 次に、Cloud のリソースや API を使用するために、Cloud コンソールで課金を有効にする必要があります。この Codelab の操作をすべて行って、費用が生じたとしても、少額です。このチュートリアルの終了後に請求が発生しないようにリソースをシャットダウンするには、作成したリソースを削除するか、プロジェクト全体を削除します。Google Cloud の新規ユーザーは、300 米ドル分の無料トライアル プログラムをご利用いただけます。
Cloudshell エディタを起動する
このラボは、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)')
このラボで使用するインフラストラクチャをプロビジョニングする
このラボでは、GKE にコードをデプロイし、Spanner データベースに保存されているデータにアクセスします。また、IDE として Cloud Workstations も使用します。次の設定スクリプトは、このインフラストラクチャを準備します。
- 設定スクリプトをダウンロードして実行可能にします。
wget https://raw.githubusercontent.com/GoogleCloudPlatform/container-developer-workshop/main/labs/python/setup_with_cw.sh
chmod +x setup_with_cw.sh
setup_with_cw.shファイルを開き、現在 CHANGEME に設定されているパスワードの値を編集します。- 設定スクリプトを実行して、このラボで使用する GKE クラスタと Spanner データベースを起動する
./setup_with_cw.sh &
Cloud Workstations クラスタ
- Cloud Console で Cloud Workstations を開きます。クラスタが
READYステータスになるまで待ちます。
ワークステーションの構成を作成する
- Cloud Shell セッションが切断された場合は、[再接続] をクリックしてから、gcloud CLI コマンドを実行してプロジェクト ID を設定します。コマンドを実行する前に、以下のサンプル プロジェクト ID を Qwiklabs プロジェクト ID に置き換えます。
gcloud config set project qwiklabs-gcp-project-id
- ターミナルで次のスクリプトをダウンロードして実行し、Cloud Workstations 構成を作成します。
wget https://raw.githubusercontent.com/GoogleCloudPlatform/container-developer-workshop/main/labs/python/workstation_config_setup.sh
chmod +x workstation_config_setup.sh
./workstation_config_setup.sh
- [構成] セクションで結果を確認します。READY ステータスに移行するまでに 2 分かかります。

- コンソールで Cloud Workstations を開き、新しいインスタンスを作成します。

- 名前を
my-workstationに変更し、既存の構成codeoss-pythonを選択します。

- [ワークステーション] セクションで結果を確認します。
ワークステーションを起動
- ワークステーションを起動します。ワークステーションの起動には数分かかります。

- アドレスバーのアイコンをクリックして、サードパーティ Cookie を許可します。


- [サイトが動作していない場合] をクリックします。

- [Cookie を許可] をクリックします。

- ワークステーションが起動すると、Code OSS IDE が表示されます。ワークステーション IDE の [スタートガイド] ページで [完了] をクリックします。

3. 新しい Python スターター アプリケーションを作成する
このセクションでは、新しい Python アプリケーションを作成します。
- 新しいターミナルを開きます。

- 新しいディレクトリを作成してワークスペースとして開く
mkdir music-service && cd music-service
code-oss-cloud-workstations -r --folder-uri="$PWD"
このメッセージが表示されたら、[許可] ボタンをクリックして、ワークステーションにコピー&ペーストできるようにします。

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
プロンプトが表示されたら、矢印キーでカーソルを移動し、スペースバーでオプションを選択します。
次のいずれかを選択:
- ポートの
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 し、your アプリケーションを GKE にデプロイします。これは舞台裏で行われ、デベロッパー フローから詳細が抽象化されます。
Google Cloud にログインする
- Cloud Code アイコンをクリックし、[Sign in to Google Cloud] を選択します。

- [ログインに進む] をクリックします。

- ターミナルで出力を確認し、リンクを開きます。

- Qwiklabs の受講者用認証情報でログインします。

- [許可] を選択します。

- 確認コードをコピーして、[ワークステーション] タブに戻ります。

- 確認コードを貼り付けて Enter キーを押します。

Kubernetes クラスタを追加する
- クラスタを追加する

- [Google Kubernetes Engine] を選択します。

- プロジェクトを選択します。

- 初期設定で作成された「python-cluster」を選択します。

- これで、クラスタが Cloud Code の Kubernetes クラスタのリストに表示されます。ここからクラスタをナビゲートして探索します。

gcloud CLI を使用して現在のプロジェクト ID を設定する
- このラボのプロジェクト ID を Qwiklabs ページからコピーします。

- ターミナルから gcloud CLI コマンドを実行して、プロジェクト ID を設定します。コマンドを実行する前に、サンプル プロジェクト ID を置き換えます。次のコマンドを実行する前に、プロジェクト ID を置き換えてください。
gcloud config set project qwiklabs-gcp-project-id
Kubernetes へのデプロイ
- Cloud Shell エディタの下部にあるペインで、Cloud Code  を選択します。

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

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

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

ビルドとテストが完了すると、[出力] タブのログに URL http://localhost:8080 が [Kubernetes: Run/Debug] ビューに表示されます。
- 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モードでアプリケーションを実行します。
OutputウィンドウのKubernetes Run/Debug - Detailedビューで、skaffold がこのアプリケーションをデバッグモードでデプロイすることを確認します。
- プロセスが完了したとき。デバッガが接続され、[出力] タブに
Attached debugger to container "python-app-8476f4bbc-h6dsl" successfully.と表示され、URL http://localhost:8080 が表示されます。
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と書かれたステートメントを見つけます。- 行番号の左側にある余白をクリックして、その行にブレークポイントを追加します。ブレークポイントが設定されたことを示す赤いインジケーターが表示されます。
- 初回実行時には、コンテナ内のソースの場所を尋ねるプロンプトが表示されます。この値は Dockerfile のディレクトリに関連しています。
Enter キーを押してデフォルトを受け入れます

アプリケーションのビルドとデプロイには数分かかります。
- ブラウザを再読み込みします。デバッガがブレークポイントでプロセスを停止し、GKE でリモートで実行されているアプリケーションの変数と状態を調べることができます。
- [変数] セクションをクリックします。
- [Locals] をクリックすると、
"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"
変更後の仕様セクションは次のようになります。
spec:
containers:
- name: python-app
image: python-app
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」が表示されます。 - エントリをいくつか追加します。
Cloudshell ターミナルから、次のコマンドを実行します。
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_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 ~/music-service
- プロジェクトを削除して、関連するインフラストラクチャとリソースをすべて削除する
