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

[Container-Optimized OS](https://cloud.google.com/container-optimized-os/docs/concepts/features-and-benefits?hl=ja)を使って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イメージをプッシュ
1. GCRのイメージからGCE上のVMにコンテナをデプロイ。コンテナ処理終了後、VMを削除

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

```sh: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にコンテナをデプロイするスクリプトです。

```sh: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を削除するモジュールです。

```python: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削除処理を行います。

```python: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?](https://stackoverflow.com/questions/52748332/how-to-make-gce-instance-stop-when-its-deployed-container-finishes)
[Google公式ドキュメント](https://cloud.google.com/container-optimized-os/docs/how-to/create-configure-instance?hl=ja)