GCE上でContainer-Optimized OSを使ってコンテナバッチ処理する方法


Container-Optimized OSを使ってGCE上でコンテナバッチ処理する方法のメモ。
コンテナバッチ処理後はコンテナ内でVMを削除するため、VM稼働分のコストで済みます。
コードはGithubにあげています。
https://github.com/yolo-kiyoshi/gce_docker_cli

フォルダ構成
root/
 ├ .env
 ├ .param
 ├ Dockerfile
 ├ Pipfile
 ├ Pipfile.lock
 ├ *********.json(credentialファイル)
 ├ scripts/
 │ └ build_and_push.sh
 │ └ deploy_container.sh
 └ src/
   └ delete_vm.py
   └ main.py

準備

VM起動パラメータ

VM起動パラメータを .param に記載します。

SERVICE_ACCOUNT=*********
PROJECT_ID=*********
MACHINE_TYPE=n1-standard-1

Credentialファイル

Dockerfile と同一ディレクトリにCredentialファイル(json)を配置します。

コンテナ環境変数

コンテナ環境変数を .env に記載します。
Credentialファイル(json)pathを必ず GOOGLE_APPLICATION_CREDENTIALS に記載します。

GOOGLE_APPLICATION_CREDENTIALS=/app/************.json

概要

  1. GCRにDockerイメージをプッシュ
  2. GCRのイメージからGCE上のVMにコンテナをデプロイ。コンテナ処理終了後、VMを削除

GCRにDockerイメージをプッシュ

Dockerイメージをビルドし、GCRにイメージをプッシュするスクリプトです。

scripts/build_and_push.sh
#!/usr/bin/env bash

set -eu

image=${1:-}
tag=${2:-latest}

# read param
. .param

fullname="gcr.io/${PROJECT_ID}/${image}:${tag}"

# read setting file
docker build -t ${image}:${tag} .
docker tag ${image}:${tag} ${fullname}
docker push ${fullname}
echo "image successfully pushed to: ${fullname}"

Dockerfile と同一ディレクトリで以下を実行すると、GCRにDockerイメージをプッシュできます。

Dockerビルド&GCRへのプッシュ
sh scripts/build_and_push.sh <イメージ名> <タグ>

GCRのイメージからGCE上のVMにコンテナをデプロイ。コンテナ処理終了後、VMを削除

GCRのイメージからGCE上のVMにコンテナをデプロイするスクリプトです。

deploy_container.sh
#!/usr/bin/env bash

set -eu

service=${1:-}
image=${2:-}
tag=${3:-latest}

# read param
. .param

fullname="gcr.io/${PROJECT_ID}/${image}:${tag}"

gcloud compute instances create-with-container ${service} \
    --container-image=${fullname} \
    --container-env-file=.env \
    --container-restart-policy=never \
    --machine-type=${MACHINE_TYPE} \
    --service-account=${SERVICE_ACCOUNT}

VMを削除するモジュールです。

src/delete_vm.py
from googleapiclient import discovery

from oauth2client.client import GoogleCredentials

import requests


def delete_vm():
    # credentialオブジェクト取得
    credentials = GoogleCredentials.get_application_default()
    service = discovery.build('compute', 'v1', credentials=credentials)
    # メタデータサーバからプロジェクトID, インスタンンス名, ゾーンを取得する
    project_id = requests.get(
        "http://metadata.google.internal/computeMetadata/v1/project/project-id",
        headers={"Metadata-Flavor": "Google"}
    ).text
    name = requests.get(
        "http://metadata.google.internal/computeMetadata/v1/instance/name",
        headers={"Metadata-Flavor": "Google"}
    ).text
    zone_long = requests.get(
        "http://metadata.google.internal/computeMetadata/v1/instance/zone",
        headers={"Metadata-Flavor": "Google"}
    ).text
    zone = zone_long.split("/")[-1]

    print(f'[delete target] project_id:{project_id},name:{name},zone:{zone}')
    # VMインスタンス削除
    request = service.instances().delete(
        project=project_id,
        zone=zone,
        instance=name
    )
    request.execute()

main の最後(finally)でVM削除処理を行います。

src/main.py
from delete_vm import delete_vm


try:
    print('process start.')
    ######################
    # process
    ######################
    print('process end.')
finally:
    delete_vm()

Dockerfile と同一ディレクトリで以下を実行すると、GCRのDockerイメージをもとにVMにコンテナをデプロイします。
コンテナ内での処理終了後はVMを削除します。

sh scripts/deploy_container.sh <サービス名> <イメージ名> <タグ>

参考

How to make GCE instance stop when its deployed container finishes?
Google公式ドキュメント