GAEを利用してDatastoreのバックアップを取得する
単にバックアップを取得するのであれば特にプログラムを書く必要はなく、こちら(https://cloud.google.com/appengine/articles/scheduled_backups) の記事にあるように 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名はカンマ区切りで入力できるように。
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:
- description: Daily Backup
url: /backup
schedule: every day 02:00
timezone: Asia/Tokyo
target: default
queue設定。このqueueのtargetが重要
queue:
- name: backupQueue
rate: 1/s
target: ah-builtin-python-bundle
メインのプログラム。
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