1. 概要
このラボでは、コンテナ化された環境で Python アプリケーションの開発を担当するソフトウェア エンジニアの開発ワークフローを効率化するために設計された機能と機能について説明します。一般的なコンテナ開発では、コンテナの詳細とコンテナ ビルド プロセスを理解する必要があります。また、デベロッパーは通常、IDE から離れてリモート環境でアプリケーションをテストしてデバッグする必要があるため、フローが中断されます。このチュートリアルで説明したツールとテクノロジーを使用すると、開発者は IDE を離れることなく、コンテナ化されたアプリケーションを効率的に操作できます。
学習内容
このラボでは、GCP でコンテナを使用して開発する方法について学びます。
- 新しい Python スターター アプリケーションの作成
- 開発プロセスを説明する
- シンプルな CRUD REST サービスを開発する
2. 設定と要件
セルフペース型の環境設定
- Google Cloud コンソール にログインして、プロジェクトを新規作成するか、既存のプロジェクトを再利用します。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 米ドル分の無料トライアル プログラムをご利用いただけます。
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)')
ソースコードを取得する
- このラボのソースコードは、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
プロンプトが表示されたら、矢印キーでカーソルを移動し、スペースバーでオプションを選択します。
次のいずれかを選択:
- ポートの
8080 y: 構成を保存します。
Skaffold 構成を更新する
- デフォルトのアプリケーション名を変更する
skaffold.yamlを開く- 現在
dockerfile-imageに設定されているイメージ名を選択します。 - 右クリックして [すべての出現箇所を変更] を選択します。
- 新しい名前を
python-appとして入力します。 - ビルド セクションをさらに編集して、
- パス
FLASK_DEBUG=1にdocker.buildArgsを追加 - 同期設定により、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 で実行] を選択します。プロンプトが表示されたら、[Yes] を選択して現在の Kubernetes コンテキストを使用します。

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

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

ビルドとテストが完了すると、[出力] タブに 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モードでアプリケーションを実行します。
OutputウィンドウのKubernetes Run/Debug - Detailedビューで、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 でリモート実行されているアプリケーションの変数と状態を調べることができます。
- [変数] セクションをクリックします。
- [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"
アプリケーションをデプロイして検証する
- 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 container-developer-workshop
- プロジェクトを削除して、関連するインフラストラクチャとリソースをすべて削除する