LoginSignup
21
11

More than 5 years have passed since last update.

Datastoreのバックアップを取得する

Last updated at Posted at 2017-03-25

GAEを利用してDatastoreのバックアップを取得する

単にバックアップを取得するのであれば特にプログラムを書く必要はなく、こちら(https://cloud.google.com/appengine/articles/scheduled_backups) の記事にあるように cron.yaml に以下のような記述をすれば定時のバックアップができる。

cron.yaml
cron:
- description: My Daily Backup
  url: /_ah/datastore_admin/backup.create?name=BackupToCloud&kind=LogTitle&kind=EventLog&filesystem=gs&gs_bucket_name=whitsend
  schedule: every 12 hours
  target: ah-builtin-python-bundle

ただ、 cron.yaml にbucket名まで記述することになるため、1つのコードの記述で 本番環境 ステージング環境 とbucketを切り替えることができない。
そのため、goのコードからDatastoreのバックアップを取得を試みた。

早速実装

処理に必要なファイルと実装は以下。

goのアプリケーションをデプロイするための記述と、出力先バケット名とバックアップの出力のprefixを記述している。
あと、バックアップ対象から除外したいKind名はカンマ区切りで入力できるように。

app.yaml
application: hogehoge-backup-test
version: 1
runtime: go
api_version: go1

handlers:
- url: /.*
  script: _go_app
  login: admin

env_variables:
  TARGET_BUCKET_NAME: "hogehoge-backup-bucket"
  BACKUP_PREFIX: "backup-prefix"
#  IGNORE_KINDS: "b,c" # backupから除外したいkind名をカンマ区切りで

cron(定時実行の設定)を記述。

cron.yaml
cron:
- description: Daily Backup
  url: /backup
  schedule: every day 02:00
  timezone: Asia/Tokyo
  target: default

queue設定。このqueueのtargetが重要

queue.yaml
queue:
- name: backupQueue
  rate: 1/s
  target: ah-builtin-python-bundle

メインのプログラム。

backup.go
import (
    "fmt"
    "golang.org/x/net/context"
    "google.golang.org/appengine"
    "google.golang.org/appengine/datastore"
    "google.golang.org/appengine/taskqueue"
    "net/http"
    u "net/url"
    "os"
    "strings"
)

func init() {
    http.HandleFunc("/backup", handler)
}

const (
    BackupQueueName = "backupQueue"
    BackupPath      = "/_ah/datastore_admin/backup.create"
)

func handler(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    bucketName := os.Getenv("TARGET_BUCKET_NAME")
    backupPrefix := os.Getenv("BACKUP_PREFIX")
    ignoreKindsString := os.Getenv("IGNORE_KINDS")

    kinds, err := getKinds(c)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        fmt.Fprint(w, http.StatusText(http.StatusInternalServerError))
        return
    }

    q := u.Values{
        "name":           {fmt.Sprintf("%s-", backupPrefix)},
        "filesystem":     {"gs"},
        "gs_bucket_name": {bucketName},
    }

    ignoreMap := map[string]bool{}
    if ignoreKindsString != "" {
        ignoreKinds := strings.Split(ignoreKindsString, ",")
        for _, kind := range ignoreKinds {
            ignoreMap[kind] = true
        }
    }

    for _, kind := range kinds {
        if _, ok := ignoreMap[kind]; !ok {
            q.Add("kind", kind)
        }
    }

    backupTask := taskqueue.NewPOSTTask(BackupPath, q)

    if _, err := taskqueue.Add(c, backupTask, BackupQueueName); err != nil {
        // fail
        w.WriteHeader(http.StatusInternalServerError)
        fmt.Fprint(w, http.StatusText(http.StatusInternalServerError))
    } else {
        // success
        w.WriteHeader(http.StatusOK)
        fmt.Fprint(w, http.StatusText(http.StatusOK))
    }
}

func getKinds(c context.Context) ([]string, error) {
    t := datastore.NewQuery("__kind__").KeysOnly().Run(c)
    var kinds []string
    for {
        key, err := t.Next(nil)
        if err == datastore.Done {
            break
        }
        if err != nil {
            return nil, err
        }
        if strings.HasPrefix(key.StringID(), "_") {
            continue
        }
        kinds = append(kinds, key.StringID())
    }
    return kinds, nil
}

だいたいこんな感じ。
ディレクトリに上記のファイルを配置して goapp deploy ./ と実行するだけで(app.yamlの内容は各自環境に合わせる必要はありますが) appengine環境にデプロイして動作します。

上のコードでは最初に言っていたBucket名をプロジェクトごとに切り替えることができていないが、Bucket名にプロジェクト名を含めるなどとし、 appengine.AppID(context) でappengineのプロジェクト名を取得してBucket名の切り替えなどを行うことで対応できます。

上記のコードでできていること

  • Datastore内の _ prefix のついていないKind一覧を取ってバックアップ(prefixがついているものは各種メタ的な情報(?)なので)
  • 全部取られたくない場合のため、無視させたいKindの一覧をわたせるようにした。

一般的なバックアップしたいという要求はみたせるのではないかと。

ソースコード

githubに置きました。

謝辞

@sinmetal さん、 @soundTricker さん、Datastoreのバックアップに関する情報ありがとうございました。
@vvakame さん一般的なHandlerに書き直してみました。ありがとうございます。
m(_ _)m

21
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
11