LoginSignup
0
0

More than 5 years have passed since last update.

Go版のGoogleCloudFunctionsのプロジェクト導入を考えてみる

Posted at

Go版のGoogleCloudFunctionsのプロジェクト導入を考えてみる

GCFについて色々調べてみたり手元で実行してきたが、
プロジェクト導入 という観点から調査・動作確認してみる。

課題・疑問

今までGCFについて調査してきた上で出てきた課題や不明点について

  • ディレクトリ構成どうなるの?
  • 外部、ローカル依存パッケージの取り込みどうすればよい?
  • デプロイは1つずつ

他にもあるのかもしれないが、一旦上記について考えていく。

ディレクトリ構成どうなるの?

HelloWorldのようにシンプルな関数の場合

これはあまり深く考えない。

$ tree
.
├── foo.go
├── foo_test.go
└── go.mod

少し大きくなってきて関数を複数に分けたい、別ファイルに分けたいといった場合には、

  • foo.goの中でprivate関数を作成する
  • 別ファイルに切り出してfooの中から参照
    • foo.goと同一パッケージとして作成

あまり実用的でないのでこれについてはこのくらいで。

少し複雑になってきた場合

データストア連携部分などが必要になってきて、その部分を別パッケージとして追加したい場合など。

GCFのGoからCloudSQLに接続するサンプルを利用して考えてみることにする。
CloudSQL連携部分を datastore/mysql/mysql.go として置く。

ディレクトリ構成

$ tree
.
├── datastore
│   └── mysql
│       └── mysql.go
├── fuga.go
├── fuga_test.go
└── go.mod

fuga.go

fuga.go
package fuga

import (
    "fmt"
    "net/http"

    sql "path/to/fuga/datastore/mysql"
)

func Fuga(w http.ResponseWriter, r *http.Request) {
    now := sql.MySQLDemo()
    fmt.Fprintf(w, "Now is %s", now)
}

mysql.go

mysql.go
// Package sql contains examples of using Cloud SQL.
package sql

import (
    "database/sql"
    "fmt"
    "log"
    "os"

    // Import the MySQL SQL driver.
    _ "github.com/go-sql-driver/mysql"
)

var (
    db *sql.DB

    connectionName = os.Getenv("MYSQL_INSTANCE_CONNECTION_NAME")
    dbUser         = os.Getenv("MYSQL_USER")
    dbPassword     = os.Getenv("MYSQL_PASSWORD")
    dsn            = fmt.Sprintf("%s:%s@unix(/cloudsql/%s)/", dbUser, dbPassword, connectionName)
)

func init() {
    var err error
    db, err = sql.Open("mysql", dsn)
    if err != nil {
        log.Fatalf("Could not open db: %v", err)
    }

    // Only allow 1 connection to the database to avoid overloading it.
    db.SetMaxIdleConns(1)
    db.SetMaxOpenConns(1)
}

// MySQLDemo is an example of making a MySQL database query.
// ※公式のサンプルから現在日時を返却するだけの関数に変更
func MySQLDemo() string {
    rows, err := db.Query("SELECT NOW() as now")
    if err != nil {
        log.Printf("db.Query: %v", err)
        return ""
    }
    defer rows.Close()

    now := ""
    rows.Next()
    if err := rows.Scan(&now); err != nil {
        log.Printf("rows.Scan: %v", err)
        return ""
    }

    return now
}

buildを実行してみる

$ go build
fuga.go:7:2: unknown import path "path/to/fuga/datastore/mysql": cannot find module providing package path/to/fuga/datastore/mysql

上記エラーが出てビルドが通らない・・・
go1.11使っているから datastore/mysql もモジュールとして機能させないとダメかな?と思い datastore/mysql 下で go mod init をやってみる。
go.modファイルができたのでもう一度ビルドしてみるが、結果は同じエラーが出て失敗。

GOPATHが通っている状況下でこんなことあったかな・・・と思いつつ諸々ググってみる。
※go1.11からはGOPATHを意識する必要がなくなったとどっかで見た覚えが

ググってみたところ公式に以下の記載を見つける

https://github.com/golang/go/wiki/Modules#when-should-i-use-the-replace-directive
https://github.com/golang/go/wiki/Modules#can-i-work-entirely-outside-of-vcs-on-my-local-filesystem

特に

This is very simple if you have a single module you want to edit at a time outside of VCS (and you either have only one module in total, or if the other modules reside in VCS). In this case, you can place the file tree containing the single go.mod in a convenient location. Your go build, go test and similar commands will work even if your single module is outside of VCS (without requiring any use of replace in your go.mod).

If you want to have multiple inter-related modules on your local disk that you want to edit at the same time, then replace directives are one approach. Here is a sample go.mod that uses a replace with a relative path to point the hello module at the on-disk location of the goodbye module (without relying on any VCS):

この部分にあるようにgithubのようなVersionControllSystem以外の例えばローカルにあるようなファイルについては、go.mod内で replaceディレクティブ を使えば良いとある。

んーなるほど分からん。
なので試してみる。

go.mod
module path/to/fuga

require (
    path/to/fuga/datastore/mysql v0.0.0
)

replace path/to/fuga/datastore/mysql v0.0.0 => ./datastore/mysql

↑で作成したdatastore/mysqlを requirereplace に上記のような記述を追加してあげる。これでビルドしたら無事通った。

ちなみに、 replace だけでは

fuga.go:7:2: unknown import path "path/to/fuga/datastore/mysql": cannot find module providing package path/to/fuga/datastore/mysql

というエラーが出て通らなかったので注意。
やっとこれで先に進める。

デプロイ

上記のreplaceを使ったやり方でビルドが通ったのでGCFにデプロイしてみる。

$ gcloud functions deploy fuga --entry-point Fuga --runtime go111 --trigger-http
Deploying function (may take a while - up to 2 minutes)...failed.
ERROR: (gcloud.functions.deploy) OperationError: code=3, message=Build failed: go: finding path/to/fuga/datastore/mysql v0.0.0
go: finding github.com/go-sql-driver/mysql v1.4.1
go: path/to/fuga/datastore/mysql@v0.0.0: unknown revision fuga/datastore/mysql/v0.0.0
go: error loading module requirements

意気揚々とデプロイしてサクッと撃沈しました。datastore/mysqlが見つからないよう。
ローカルのパッケージを指定するためにreplaceしていたので当然っちゃ当然なのですが・・・

ちょっと調べてみたのですが、シンプルに直感的に解決できるような方法は見つからず。

暫定対応

githubにプッシュして確認とったわけではないので絶対とは言えないが・・・
go.modに追加したrequireとreplaceを消した上で

githubにプッシュ→デプロイ

と実行すればデプロイできないってエラーは解消される気がする。

恒久対応はどうしたらいい?

  • go.modを環境別で使用できる?
  • GOPROXYなるものを利用してlocalにproxyサーバー立てて取得先をうまいことやる?
  • その他には?

もし詳しい人がいたら是非教えてください・・・

デプロイは1つずつ

デプロイは関数一つずつなので、CIやCloudBuilderなどを使って地道にデプロイコマンド書いていく。
個人的にはCI上でコマンド丸ごと管理するよりは、CloudBuilderをつかてエントリポイントなど重要なところに集中できるような管理が良いと感じた。

まとめ

開発中のローカルパッケージの依存関係問題を解消しないとプロジェクトへの導入は難しいのではないかと。
デプロイについては、ちょっと泥臭くなってしまうが大きな障壁とはならないはず。

なので次はGOPROXYを利用したパッケージ管理方法を調査してみることとする。

0
0
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
0
0