- 投稿日:2020-11-04T22:32:47+09:00
Microsoft AzureのGo紹介
2020年11月3日(火)にオンラインで「合同勉強会 in 大都会岡山 -2020 Winter Online-」が開催されました。今回は初のオンライン開催でしたが、バラエティ豊かなセッションが揃った、これまで通りの雰囲気だったので安心しました。
2020年、これまでの「Azure SDK for Go」の学習の過程で私が知り得たMicrosoft Azureな情報のお裾分けと、裏テーマとしてお世話になった方々への恩返しとして、「Microsoft AzureのGo紹介」にて発表させて頂きました。
直前まで何回リハをしても予定の20分を超えてしまいどうなるかと心配していましたが、いざ本番、ほぼ時間通りに終われたということで、ひと安心。
現時点での日本のMicrosoft、Azure、コミュニティの情報と、今回は少しとなってしまった関連するGoの情報を紹介しています。あなたの知らないAzureがあるかも!?
- 投稿日:2020-11-04T22:17:18+09:00
go の json 変換の挙動まとめ
go の json 挙動を色々と試したのでそれをまとめます。
json への変換の仕方
まずは、基本的なjsonへの変換の仕方です。 最も簡単な方法としては、以下のような感じで変換を行ないます。
import "encoding/json" //... jsonout, err := json.Marshal(対象のオブジェクト)このやり方は分かりやすいですが、実際は以下のような形で変換することが多いと思います。
encoder := json.NewEncoder(jsonの書きこみ先のio.Writer) err := encoder.Encode(対象のオブジェクト)上記のやり方だと、
io.Writer
のインターフェイスであれば何でもいけるので、ファイル(os.File)に書きこんだり、httpのレスポンス(http.ResponseWriter)に書きこんだり、バッファ(bufio.Writer)に書きこんだりと自由度が高いです。基本的な出力
次に基本的な構造体の出力を見ます。 以下のような普通の構造体の場合のjsonの出力内容は以下のようになります。
func main() { type Sample struct { IDString string } st := Sample{IDString: "xxfff"} out, _ := json.Marshal(st) fmt.Println(string(out)) // []byte型なのでstringに変換 // Output: // {"IDString":"xxfff"} }構造体がそのままjsonに変換されるイメージです。直感的です。しかし、jsonの命名規則を考えると実用的かと言われるかと微妙です。 多くの人は json のキー名はローワーキャメルで、スネークケースにすることが多いと思います。そのためには、キー名を変更する必要があります。キー名を変更するには tag 文字を使用して変更します。 キー名を変更したケースとしては、以下のようになります。
func main() { type Sample struct { IDString string `json:"id_string"` } st := Sample{IDString: "xxfff"} out, _ := json.Marshal(st) fmt.Println(string(out)) // []byte型なのでstringに変換 // Output: // {"id_string":"xxfff"} }いちいちタグ文字を入れるのが面倒と思う人は多いです。しかし、公式のjsonライブラリにはいい感じのオプションはないため、ツールを使ったりして、みなさん頑張っています。
jsonタグについて
json タグのフォーマットは以下のようになります。
`(...) json:"[<key>][,<flag1>[,<flag2>]]" (...)`上記のように、json タグはキー名を変える以外にも指定できるものがあります。指定できる項目としては以下です。
- キー名
- omitempty
- string
キー名
この項目はその名の通り json の キー名のフィールドになります。後述しますが、キー名の指定の中でも最も優先度の高いものとなります。
-
を指定した場合は、そのフィールドをスキップすることになります。 何も書かず,
のみの場合は、通常通り構造体のフィールドの名前がそのまま使われます。omitempty
この項目を指定した場合は、値がゼロ値のさいにスキップされます。go 言語の場合は、初期値がゼロ値として扱われるため、ポインタ型以外の型ではこの項目は使わないかなって思います。 イメージとしては以下のような感じです。
func main() { type Sample struct { ID string `json:",omitempty"` Pointer *string `json:",omitempty"` } s := Sample{ID: "", Pointer: nil} out, _ := json.Marshal(s) fmt.Println(string(out)) // []byte型なのでstringに変換 // Output: // {} }string
この項目は個人的に結構特殊な項目で、値をstring型に変更します。対応しているのは以下の組み込み型となっています。
- string
- byte
- rune
- int
- int8
- int16
- int32
- int64
- uint
- uint8
- uint16
- uint32
- uint64
- float32
- float64
- bool
- uintptr
また、string型も、string型に直されるため、
"
がエスケープされて出力されます。func main() { type Sample struct { ID string `json:",string"` } s := Sample{ID: "xxffid"} out, _ := json.Marshal(s) fmt.Println(string(out)) // []byte型なのでstringに変換 // Output: // {"ID":"\"xxffid\""} }出力順
出力順は構造体のフィールドの順番と同様になります。しかし、map のような順番が定まっていないものは、キー名のアルファベット順にソートがかけられます。何でこの順番と思いましたが、テストのしやすさを考えるとそんなもんかなぁという気持ちになります。
func main() { s := map[string]string{ "cup": "one", "apple": "two", "banana": "three", } out, _ := json.Marshal(s) fmt.Println(string(out)) // []byte型なのでstringに変換 // Output: // {"apple":"two","banana":"three","cup":"one"} }埋め込み型
go 言語の構造体には埋め込み型が可能です。この型はフィールド名を指定ない型のためキー名も存在しません。そのため、基本的には埋め込まれた構造体のフィールドが展開されて出力されます。
func main() { type Emb struct { Content string } type Sample struct { Emb } s := Sample{Emb: Emb{Content: "string"}} out, _ := json.Marshal(s) fmt.Println(string(out)) // []byte型なのでstringに変換 // Output: {"Content":"string"} }基本的と言ったように勿論例外があります。go言語では配列に対して型宣言することが可能です。この場合、展開を行なうと、配列が展開されることになるため、キーの名前が分かりません。そのため、配列の埋め込み型の場合は例外的に、型名がキー名となります。
func main() { type Emb []string type Sample struct { Emb } s := Sample{Emb: Emb{"content1", "content2"}} out, _ := json.Marshal(s) fmt.Println(string(out)) // []byte型なのでstringに変換 // Output: // {"Emb":["content1","content2"]} }キー名の優先順位
これまでの説明で、キー名は様々な要因で決まることがわかります。しかし、それではキーの衝突が起こってしまいます。そのため、goのjson変換には優先順位が定められています。 優先順位は以下で示すものです。
- tag で指定したキー名
- 通常のフィールド名
- 埋め込み型(埋め込み型のフィールドにも同じ優先順位が適用されます)
もし同じ優先度のものが複数あった場合は、どのキーの値も出力はされませんし、エラーも出力されません。埋め込み型を展開するさいなどに、気をつけないと嵌りそうな挙動です。
宣伝
色々と挙動がありますが、全部覚えるのも一々考えるのも面倒という人は多いと思います。そこで、構造体がどのような json を出力するか調べるツールを作成しました。 https://github.com/komem3/stout
使い方としては、以下のようにファイルパスと、構造体名を指定するだけです。
stout -path ./define.go SimpleStruct
この宣伝のために書いたのでした。
参考文献
- 投稿日:2020-11-04T12:04:59+09:00
golang × SQLite3 でLIKE句を使う
はじめに
Go言語の基礎を1~2週間ほど学び、CRUD機能を持つ簡単なwebアプリを作成しました。
その過程のデーターベース操作で躓き(LIKE句)調べても中々解決に繋がる記事を見つけられなかったので
この記事を書くことにしました。https://qiita.com/__init__/items/2edfc7acf11234e5b1aa
上記の記事にて基本的なデータベース操作は書かれています。
初めてQiita記事を書くのでとりあえず今回は自分が躓いたLIKE句のことのみ記事にしてみます。環境
バージョン:go 1.15.3
db:SQLite3
PC:Mac
事前準備
golang、SQLite3、go-sqlite3のインストール
※先ほどの記事でインストール手順も詳しく書かれています。
https://qiita.com/__init__/items/2edfc7acf11234e5b1aaテーブル作成と中身
まずはテーブル作成
package main import ( "database/sql" _ "github.com/mattn/go-sqlite3" "log" ) var DbConnection *sql.DB type Uset struct { Id int Name string Age int } func main() { DbConnection, err := sql.Open("sqlite3", "../example.sql") if err != nil { log.Fatalln(err) } defer DbConnection.Close() cmd := `CREATE TABLE IF NOT EXISTS user(id INTEGER PRIMARY KEY, name STRING, age INT)` _, err = DbConnection.Exec(cmd) if err != nil { log.Fatalln(err) } }結果
$ sqlite3 example.sql SQLite version 3.28.0 2019-04-15 14:49:49 Enter ".help" for usage hints. sqlite> .table user sqlite>データの中身
sqlite> select * from user; 1|yamada|20 2|yamamoto|22 3|suzuki|19 4|tanaka|15 5|miyamoto|30 sqlite>レコードを5つ入れておきました。
左から、id name age ですLIKE句
うまくいったコード
cmd := "SELECT * FROM user WHERE name LIKE ?" rows, err := Dbconnection.Query(cmd, "%yamada%") if err != nil { log.Println(err) } defer rows.Close() var users []User for rows.Next() { var user User err = rows.Scan(&user.Id, &m.Name, &m.Age) if err != nil { log.Println(err) } users = append(users, user) fmt.Println(users)結果
$ go run main.go [{1 yamada 20}]
うまくいかなかったコード
cmd := "SELECT * FROM user WHERE name LIKE '%?%'" //←ここ rows, err := DbConnection.Query(cmd, "yamada") // 結果 go run main.go []『?』 の部分に値を渡すのですが
先に『%%』で囲っておくのではなく『"%yamada%"』のように『%』と一緒に値を渡さないとダメでした。
Python×SQLiteのLIKE句の記事を見つけ参考にしました。
ターミナルなどではsqlite> select * from user where name like '%yamada%'; 1|yamada|20でいけます。
整数も同じようにいけます
cmd := "SELECT * FROM user WHERE age LIKE ?" rows, err := DbConnection.Query(cmd, "%1%") // 結果 [{3 suzuki 19} {4 tanaka 15}]他の使用パターン
任意の文字列から始まるレコードを取得するとき
cmd := "SELECT * FROM user WHERE name LIKE ?" rows, err := DbConnection.Query(cmd, "y%") // 結果 [{1 yamada 20} {2 yamamoto 22}]『m』が二回くるデータを取得
cmd := "SELECT * FROM user WHERE name LIKE ?" rows, err := DbConnection.Query(cmd, "%m%m%") // 結果 [{2 yamamoto 22} {5 miyamoto 30}]任意の長さのカラムを持つデータを取得(サンプルでは6文字)
cmd := "SELECT * FROM user WHERE name LIKE ?" rows, err := DbConnection.Query(cmd, "______") // 結果 [{1 yamada 20} {3 suzuki 19} {4 tanaka 15}]最後に
同じように躓く方がいるかわかりませんが、誰かの参考になると幸いです。(コードの詳細まで書きたかったのですが不慣れなせいかここまでかなり時間がかかってしまったのですいません。)
実際に簡単なアプリを作成してみてgolangはRailsのように情報量が豊富ではないと実感したので
今後golangの学習の中で気づいたことなどを記事にしていけたらと思います。
最後まで見ていただきありがとうございました。
- 投稿日:2020-11-04T08:42:23+09:00
Docker環境のgRPC/goでハローワールド
はじめに
goのお勉強しはじめた。gRPCのお勉強しはじめた。まず、これを試してみた。
思ったより、つまづいたので記録を残しておく。
Quick start – gRPCgoもgRPCもよくわかってない。
環境は、このdockerイメージを使った。名前的に、欲しいの入ってそうやったので。
grpc/go - Docker HubDocker
こんな感じで立ち上げた。Dockerよくわからん 。
docker pull grpc/go docker run --name grpc-go -it grpc/go /bin/bashQuick Start
Quick Startにそって順番に実行していく。
下記コマンドを実行すると、
$ go run greeter_server/main.goこんな感じのエラーが出る。
cannot find package "golang.org/x/net/http2"とか
undefined: "github.com/golang/protobuf/proto".ProtoPackageIsVersion4必要そうなのを入れたり、下記コマンドで入れ直したりした。
go get golang.org/x/sys/unix go get golang.org/x/net/http2 go get google.golang.org/genproto/googleapis/rpc/status go get -u github.com/golang/protobuf/proto最後のやつはコピペもとを真似して
-u
をつけたけど、よくわかってない。
updateっぽいけど。go - The Go Programming Language
The -u flag instructs get to update modules providing dependencies of packages named on the command line to use newer minor or patch releases when available. Continuing the previous example, 'go get -u A' will use the latest A with B v1.3.1 (not B v1.2.3). If B requires module C, but C does not provide any packages needed to build packages in A (not including tests), then C will not be updated.
上記エラーが出た時に、下記コマンドうったりして、消して入れ直したりしてたから、バージョンとかがよくわからなくなった。
そのうち、わかるようになりたい。rm
したけど、そういえばバイナリファイルは消してない。どうなったんやろう?上書きされたんかな?rm -rf /go/src/github.com/golang/protobuf/proto rm -rf /go/src/google.golang.org/grpc/ git clone https://github.com/grpc/grpc-goエラー出た時、こことかを参考にした。
Go Frequently Asked Questions | Protocol Buffers | Google Developers上記対応で、最初の、serverとclientの方は動いた。
その後、SayHelloAgainをすると、undefinedと言われた。$ go run greeter_client/main.go # command-line-arguments greeter_client/main.go:59:12: c.SayHelloAgain undefined (type helloworld.GreeterClient has no field or method SayHelloAgain)
main.go
にあるpb "google.golang.org/grpc/examples/helloworld/helloworld"
が怪しかったので、パスを辿って見てみると、/go/src/google.golang.org/grpc/examples/helloworld/helloworld
にhelloworld.proto
の古いやつがあった。どうするのが良いのかよくわからんかったけど、修正後の
~/grpc-go/examples/helloworld/helloworld
を上記パスに上書きコピーした。
再度、serverとclientを実行すると期待通り動いた。
とりあえず、よかった。hello world
できた。環境
最終的に動いた時の環境はこんな感じ。
go get
で入れたバージョンの確認方法がよくわからん 。
GOPATH
とかはdocker pull
したイメージでデフォルトで入ってた。
.bashrc
は空っぽでも動いてる。# go env GOARCH="amd64" GOBIN="" GOCACHE="/root/.cache/go-build" GOEXE="" GOHOSTARCH="amd64" GOHOSTOS="linux" GOOS="linux" GOPATH="/go" GORACE="" GOROOT="/usr/local/go" GOTMPDIR="" GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64" GCCGO="gccgo" CC="gcc" CXX="g++" CGO_ENABLED="1" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config" GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build743505095=/tmp/go-build -gno-record-gcc-switches"# go version go version go1.10.4 linux/amd64 # protoc --version libprotoc 3.6.0最後に
ハローワールド的なことできた。よくわからんことだらけなので、ちょっとずつ調べながら学んでこう。
- 投稿日:2020-11-04T08:39:23+09:00
Goで排他制御の仕組みを作ってみた
概要
2度目の投稿です。
Goで排他制御の仕組みを書く機会があったので、記録していきます。
今回は2種類の方法で排他制御の仕組みを作っていきます。各種ツール
- go 1.14
- VSCode(1.50.1)
- golang.org/x/sync v0.0.0-20201020160332
シナリオ
今回はあるテーブルを共有資源として、テーブルに対して複数のプロセスから処理が行われたときに、先行のプロセスが終わるまで、後続のプロセスがテーブルを操作することが出来ないようにする仕組みを作っていきます。
まずは排他制御を行わず、ゴルーチンをつかって同時にテーブルをupdateしてみます。
(sync.WaitGroupはメインのgoroutineを待機させるためのものです。詳しくはこちら。)var w sync.WaitGroup func main() { w.Add(2) go update() go update() w.Wait() } func update() { defer w.Done() for i := 0; i <= 10; i += 5 { fmt.Println("tbl update:", i*10, "%") time.Sleep(time.Second) } } // tbl update: 0 % // tbl update: 0 % // tbl update: 50 % // tbl update: 50 % // tbl update: 100 % // tbl update: 100 %2つのプロセスが同時にテーブルのupdateをしている様子がわかります。
今回はこれを先行のupdateが終わってから、後続のupdateが行われるように、下記のような出力を得られる仕組みを作っていきます。// tbl update: 0 % // tbl update: 50 % // tbl update: 100 % // tbl update: 0 % // tbl update: 50 % // tbl update: 100 %sync.Mutexを使ったパターン
まずは、sync.Mutexからです。
// メイン関数は上記と同様 var m sync.Mutex func update() { defer w.Done() defer m.Unlock() m.Lock() for i := 0; i <= 10; i += 5 { fmt.Println("tbl update:", i*10, "%") time.Sleep(time.Second) } } // tbl update: 0 % // tbl update: 50 % // tbl update: 100 % // tbl update: 0 % // tbl update: 50 % // tbl update: 100 %
update()
の冒頭でUnLock()
およびLock()
を宣言します。
後続のゴルーチンは先行のゴルーチンがUnLock()
するまで(関数の処理が終わるまで)Lock()
をかけることが出来ず、結果として先行ゴルーチンの処理の終了を待つことになります。
狙った通りの出力が得られていることが分かります。sync.Mutexは非常にシンプルで分かりやすいですが、処理を行えるプロセスは一つになります。
今回のシナリオとは少し違いますが、例えば「サーバーへの負荷を抑えるため、1000以上のプロセスが同時に実行しないようにする」などの仕組みを作ることは難しそうです。セマフォを使ったパターン
筆者はセマフォを使うのが初めてなので、セマフォがなんなのかってとこから書いていきます。
セマフォとは?
セマフォは排他制御の仕組みの一つで、鉄道線路の運行をモデル化したものです。
(semaphoreは日本語で信号機の意)
線路という共有資源を同時に使うことがないよう制御するために作られました。
セマフォはセマフォ変数、P操作、V操作の3つで成り立ちます。
- セマフォ変数
- 共有資源にアクセスできるプロセスの数
- 負にはならない
- セマフォ変数が0 or 1しかとらないものを2値セマフォ(バイナリセマフォ)、0からNをとるものをゼネラルセマフォと呼ぶ
- P操作
- セマフォ変数をデクリメントする
- あるプロセスが共有資源を確保する
- V操作
- セマフォ変数をインクリメントする
- あるプロセスが共有資源を解放する
今回のシナリオは2値セマフォで、
update()
の冒頭でP操作を、終わりにV操作を行う必要がありそうです。実装
golang.org/x/syncを使って実装します。
const semaphoreCnt = 1 var s *semaphore.Weighted = semaphore.NewWeighted(semaphoreCnt) func update() { defer w.Done() defer s.Release(1) //V操作 セマフォ変数を1増やす s.Acquire(context.TODO(), 1) //P操作 セマフォ変数を1減らす for i := 0; i <= 10; i += 5 { fmt.Println("tbl update:", i*10, "%") time.Sleep(time.Second) } } // tbl update: 0 % // tbl update: 50 % // tbl update: 100 % // tbl update: 0 % // tbl update: 50 % // tbl update: 100 %
golang.org/x/sync
のAcquire()
がP操作に,Release()
がV操作にあたります。
semaphore.Weighted
でセマフォ変数を定義し、Acquire()
及びRelease()
を用いて、増減させることで排他制御を実現しています。また、
golang.org/x/sync
では、Acquire()
,Release()
でセマフォ変数をいくつ増減させるかを調節することが出来ます。(今回は2値セマフォのため、それぞれ1増減となっています。)
これにより、各プロセスの処理の重みを定義することができます。
golang.org/x/sync
を使うことでsync.Mutex
と比べ、より複雑な排他制御の仕組みを作ることができます。参考
- 投稿日:2020-11-04T05:43:13+09:00
[GKE] Deploymentのreplicasを0にしてGoアプリにOSシグナル SIGTERM を通知
お題
表題の通り。
実際の動きとしてそうなることを確認したかっただけ。前提
- GCP環境は用意済み。
- GCPローカル設定済み。(
gcloud
コマンドが使用できる状態になっている。)kubectl
コマンドが使用できる状態になっている。- GKEクラスタ作成済み。
開発環境
# OS - Linux(Ubuntu)
$ cat /etc/os-release NAME="Ubuntu" VERSION="18.04.5 LTS (Bionic Beaver)"# gcloud
$ gcloud version Google Cloud SDK 312.0.0# kubectl
$ kubectl version Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.9", GitCommit:"4fb7ed12476d57b8437ada90b4f93b17ffaeed99", GitTreeState:"clean", BuildDate:"2020-07-15T16:18:16Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"} Server Version: version.Info{Major:"1", Minor:"17+", GitVersion:"v1.17.12-gke.2502", GitCommit:"974eff7a63e05b7eb05c9aded92fae8a3ce14521", GitTreeState:"clean", BuildDate:"2020-10-19T17:01:32Z", GoVersion:"go1.13.15b4", Compiler:"gc", Platform:"linux/amd64"}# バックエンド
# 言語 - Golang
$ go version go version go1.15.2 linux/amd64実践
ソース一式
https://github.com/sky0621/study-k8sOnGKE/tree/v0.1.0/try01
ソース
Golang
適当にWebサーバを立てておいて、OSシグナル(
SIGTERM
)を受信したらログ(GOT_NOTIFY
)を吐く。
defer
でもログを仕込んでおいて、OSシグナル受信時に、deferのログは出ないことも確認する。main.gopackage main import ( "fmt" "net/http" "os" "os/signal" "syscall" ) func main() { fmt.Println("APP_START") defer fmt.Println("DEFER") // OSシグナル(SIGTERM)の受信を待ち受ける Goroutine go func() { fmt.Println("BEFORE_NOTIFY") q := make(chan os.Signal, 1) signal.Notify(q, syscall.SIGTERM) <-q fmt.Println("GOT_NOTIFY") os.Exit(-1) }() // 適当にHTTPサーバーを立ち上げておく http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if _, err := fmt.Fprint(w, "Hello"); err != nil { fmt.Printf("HANDLE_ERROR_OCCURRED: %+v", err) } }) if err := http.ListenAndServe(":8080", nil); err != nil { fmt.Printf("SERVE_ERROR_OCCURRED: %+v", err) } fmt.Println("APP_END") }Dockerfile
何の変哲もないマルチステージビルドなDockerfile。
FROM golang:1.15 as builder WORKDIR /app COPY . . RUN go mod download RUN CGO_ENABLED=0 GOOS=linux go build -mod=readonly -v -o server FROM gcr.io/distroless/base COPY --from=builder /app/server /server CMD ["/server"]Cloud Build設定
DockerイメージはContainer Registryを使う。
cloudbuild.yamlsteps: - name: 'gcr.io/cloud-builders/docker' args: [ 'build', '-t', 'gcr.io/$PROJECT_ID/golang-app-try01', '.' ] images: - 'gcr.io/$PROJECT_ID/golang-app-try01'上記を使ってビルドする用のシェルは下記。
build.sh#!/usr/bin/env bash set -euox pipefail SCRIPT_DIR=$(dirname "$0") cd "${SCRIPT_DIR}" gcloud builds submit --config cloudbuild.yaml .デプロイ設定
Container RegistryからDockerイメージを取得する。
Podは3つ。
コンテナポートは8080(別に今回は使わないけど)。deployment.yamlapiVersion: apps/v1 kind: Deployment metadata: name: golang-app-try01 spec: replicas: 3 selector: matchLabels: app: golang-app-try01 template: metadata: labels: app: golang-app-try01 spec: containers: - name: golang-app-try01 image: gcr.io/MY_GCP_PROJECT_ID/golang-app-try01 ports: - containerPort: 8080上記を使ってデプロイするシェルは下記。
自分が使っているGCPプロジェクトのIDが必要で、それ自体はローカル環境でgcloud
コマンドから拾えるのだけど、
k8sのYamlに直接書かずにGCPプロジェクトIDを指定する方法(※ConfigMapやSecret経由ならできるのかもだけど、出来れば手軽に)を調べるのが面倒だったので、sed
で書き換え。deploy.sh#!/usr/bin/env bash set -euox pipefail SCRIPT_DIR=$(dirname "$0") cd "${SCRIPT_DIR}" project=$(gcloud config get-value project) if [[ -z "${project}" ]]; then echo -n "need project" exit 1 fi echo "${project}" sed -i -e "s/MY_GCP_PROJECT_ID/${project}/" deployment.yaml kubectl apply -f deployment.yaml sed -i -e "s/${project}/MY_GCP_PROJECT_ID/" deployment.yamlPod数を書き換えるためのシェル
replica_n.sh#!/usr/bin/env bash set -euox pipefail SCRIPT_DIR=$(dirname "$0") cd "${SCRIPT_DIR}" num=${1:-} if [ -z "${num}" ]; then echo -n "input replicas number: " read num fi kubectl scale deployment golang-app-try01 --replicas="${num}"動作確認
アプリのビルド(Dockerイメージを作成してContainer Registryに格納)
$ ./build.sh ++ dirname ./build.sh + SCRIPT_DIR=. + echo . . + cd . + gcloud builds submit --config cloudbuild.yaml . Creating temporary tarball archive of 6 file(s) totalling 1.7 KiB before compression. ・ ・ ・ DONE ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ID CREATE_TIME DURATION SOURCE IMAGES STATUS 6452c516-cfbf-4497-b536-378023cbc34d 2020-11-03T19:29:14+00:00 29S gs://XXXXXXXX_cloudbuild/source/1604431752.38075-ccb069fbb0d0413382dc79d42e5c618a.tgz gcr.io/XXXXXXXX/golang-app-try01 (+1 more) SUCCESSGKEにデプロイ
$ ./deploy.sh ++ dirname ./deploy.sh + SCRIPT_DIR=. + echo . . + cd . ・ ・ ・ + kubectl apply -f deployment.yaml deployment.apps/golang-app-try01 created ・ ・ ・Podが3つ。
$ kubectl get deployment NAME READY UP-TO-DATE AVAILABLE AGE golang-app-try01 3/3 3 3 4m19sこの時点でコンテナログを見ると、3つのPodそれぞれで、アプリ起動時とOSシグナル待受開始のログが出ていることがわかる。
Pod数を0に変更
$ ./replica_n.sh 0 ++ dirname ./replica_n.sh + SCRIPT_DIR=. + echo . . + cd . + num=0 + '[' -z 0 ']' + kubectl scale deployment golang-app-try01 --replicas=0 deployment.apps/golang-app-try01 scaledOSシグナル受信時のログ(
GOT_NOTIFY
)がそれぞれのPodのログとして出た。
defer
で仕込んでいた方のログ(DEFER
)は出ない。まとめ
GKEに載せるなら、アプリ停止時に確実に処理させたい内容は
defer
でなく、OSシグナル(SIGTERM
)受信用の Goroutine を別途立てて対応。