- 投稿日:2020-01-10T21:32:22+09:00
Golang入門記事の没ネタサンプルコード
Golang入門中です。
以前、以下の記事を書きました。
Golang入門:AtCoderなどの競技プログラミング問題を解き始めるための基本構文+その他おまけTips記事に載せようかと思い書いてみたけれど、
趣旨から外れるため除外したサンプルコードをせっかくなのでまとめて公開したいと思います。入門者によるざっくり試してみたコードの羅列ですが、参考になれば幸いです。
型宣言
基本の型から独自の型を宣言できます。
newType01.gopackage main import ( "fmt" ) // intから新しい型を宣言 type aInt int type bInt int func main() { var a aInt = 5 var b bInt = 55 hoge(a, b) // 数値はaInt型,bInt型に代入できるので実行可能 hoge(5, 55) // intだとエラーになる // エラー例: // # command-line-arguments // .\newType01.go:19:6: cannot use i (type int) as type aInt in argument to hoge // .\newType01.go:19:6: cannot use j (type int) as type bInt in argument to hoge // var i int = 5 // var j int = 55 // hoge(i, j) // 別の型として扱われるので、引数の順番を逆にするとエラーになる // エラー例: // # command-line-arguments // .\newType01.go:16:6: cannot use b (type bInt) as type aInt in argument to hoge // .\newType01.go:16:6: cannot use a (type aInt) as type bInt in argument to hoge // hoge(b, a) // 型に関連付けられたメソッドを実行 a.show() } func hoge(x aInt, y bInt) { fmt.Printf("x :%T, %v\n", x, x) fmt.Printf("y :%T, %v\n", y, y) } // 新しい型に関数を関連付けられる // 型の変数をレシーバ、関数をメソッドという func (a aInt) show() { fmt.Println(a) }実行結果$ go run newType01.go x :main.aInt, 5 y :main.bInt, 55 x :main.aInt, 5 y :main.bInt, 55 5構造体
型を組み合わせて構造体が作れます。
typeStruct01.gopackage main import ( "fmt" ) type vector struct { x, y int } func main() { var v1 vector v1.x = 5 v1.y = 55 fmt.Printf("v1: %T, %v\n", v1, v1) v2 := vector{1, 2} fmt.Printf("v2: %T, %v\n", v2, v2) fmt.Printf("subXY(): %v\n", v1.subXY()) fmt.Printf("addXY(): %v\n", v2.addXY()) } // 型宣言と同様にメソッドを定義できる func (v vector) addXY() int { return v.x + v.y } func (v vector) subXY() int { return v.y - v.x }実行結果$ go run typeStruct01.go v1: main.vector, {5 55} v2: main.vector, {1 2} subXY(): 50 addXY(): 3遅延実行
deferを使うと関数内で、最後に実行される処理を宣言できます。defer01.gopackage main import "fmt" func main() { // 関数の最後に実行される // 複数宣言した場合、後に宣言したdeferが先に実行される defer fmt.Println("a defer") defer fmt.Println("b defer") defer hoge() defer fmt.Println("c defer") fmt.Println("main") } func hoge() { defer fmt.Println("hoge defer") fmt.Println("hoge") }実行結果$ go run defer01.go main c defer hoge hoge defer b defer a defer例外処理
Golangには例外処理の構文(try ~ catchなど)がありません。
替わりに関数から返されるerrorインターフェースをハンドリングします。error01.gopackage main import ( "fmt" "strconv" ) func main() { e() } func e() { n, err := strconv.Atoi("aaa") // 関数が返す値が判定して例外処理する if err != nil { fmt.Printf("type: %T\nerr: %v\n", err, err) return } fmt.Println(n) }実行結果$ go run error01.go type: *strconv.NumError err: strconv.Atoi: parsing "aaa": invalid syntaxtypeはNumErrorですが、NumErrorにはerrorが含まれているっぽいです。
strconv.atoi.go// 抜粋 type NumError struct { Func string // the failing function (ParseBool, ParseInt, ParseUint, ParseFloat) Num string // the input Err error // the reason the conversion failed (e.g. ErrRange, ErrSyntax, etc.) }ポインタ
Golangではポインタがあります。
pointer01.gopackage main import ( "fmt" ) func main() { // &でアドレスを取得 var n int = 10 fmt.Println("nのアドレス: ", &n) fmt.Println("nの値: ", n) fmt.Println() //int型のポインタ変数宣言(アドレスを格納できる) var pointer *int // nのアドレスを代入 pointer = &n fmt.Println("pointerの値: ", pointer) // アドレスから値取得 fmt.Println("pointerの中身: ", *pointer) fmt.Println() a, b := 1, 1 addOneByValue(a) // 値渡しで加算 addOneByPointer(&b) // 参照渡しで加算 fmt.Println("a: ", a) // 値渡しなので元の変数に影響なし fmt.Println("b: ", b) // 参照渡しなので変更される fmt.Println() s := []int{1, 2, 3} fmt.Println("sのアドレス: ", &s) // スライスは&でアドレス確認できない fmt.Printf("sのアドレス: %p\n", s) slicePointer(s) //スライスは参照渡し } // 値渡しの関数 func addOneByValue(a int) { a = a + 1 } // ポインタ渡し(参照渡し)の関数 func addOneByPointer(a *int) { *a = *a + 1 } // スライスを引数にした場合の渡し方の確認 func slicePointer(s []int) { fmt.Printf("sのアドレス: %p\n", s) }実行結果$ go run pointer01.go nのアドレス: 0xc0000140a0 nの値: 10 pointerの値: 0xc0000140a0 pointerの中身: 10 a: 1 b: 2 sのアドレス: &[1 2 3] sのアドレス: 0xc000016180 sのアドレス: 0xc000016180インターフェース
インターフェースを使用することで、ポリモーフィズムを実装することができます。
interface01.gopackage main import ( "fmt" ) type person interface { // say()を実装するとインターフェースを実装したことになる say() } func personSay(p person) { p.say() } type p1 struct{} func (a p1) say() { fmt.Println("aaaaa") } type p2 struct{} func (b p2) say() { fmt.Println("bbbbb") } type p3 struct{} func (c p3) nosay() { fmt.Println(". . .") } // インターフェースを使うと、どの型も引数に取るれる、返せる関数が作れる func personShout(someone interface{}) interface{} { _, ok := someone.(person) if !ok { fmt.Print("あああああ") return "aaaaa" } else { fmt.Print("ゴゴゴゴゴゴ") return 55555555 } } func main() { a := new(p1) b := new(p2) // interfaceに関連づいたメソッド経由で実行 personSay(a) personSay(b) c := new(p3) // personSay(c) // sayを実装していないのでエラーになる // エラー例 // # command-line-arguments // .\interface01.go:41:11: cannot use c (type *p3) as type person in argument to personSay: // *p3 does not implement person (missing say method) c.nosay() // メソッドとしては実行できる // fmt.Println(personShout(a)) fmt.Println(personShout(c)) }実行結果$ go run interface01.go aaaaa bbbbb . . . ゴゴゴゴゴゴ55555555 あああああaaaaa並行処理
Golangと言えばで必ず上がるであろうGolangの強みです。
比較的簡単に並行処理の記述が可能になっています。
(そもそも並行処理が難しいですが。。)goroutine
go <関数>するとgoroutineと呼ばれる軽量なスレッドで<関数>が起動します。goroutine01.gopackage main import ( "fmt" "time" ) func main() { // 呼び出し側の処理時間内に終わるため最後まで実行される go subfunc01() // 10~20まで出力する関数だが、呼び出し側の処理が終了すると終了する go subfunc02() for i := 100; i <= 103; i++ { fmt.Println(i) time.Sleep(1 * time.Second) } fmt.Println("end main") } func subfunc01() { for i := 0; i < 3; i++ { fmt.Println(i) time.Sleep(1 * time.Second) } fmt.Println("end subfunc01") } func subfunc02() { for i := 10; i < 20; i++ { fmt.Println(i) time.Sleep(1 * time.Second) } fmt.Println("end subfunc02") }実行結果$ go run goroutine01.go 100 0 10 1 11 101 12 102 2 103 end subfunc01 13 14 end mainChannel
golangではChannelを使用して、各goroutine間でメッセージの送受信ができます。
サンプルを書いてる最中に、「これ範囲外だな」と思い書ききらなかったのでサンプルがありません。。(´・ω・`)外部パッケージの利用
他の言語同様にGolangでも公開されているパッケージの利用ができます。
外部パッケージを使用する場合はgo get <パッケージ>で取得します。ここではmysqlのドライバをgetしてDBにアクセスしてみます。
使用する外部パッケージ(mysql用のドライバ):https://github.com/go-sql-driver/mysql※以降はIPアドレス:192.168.10.10が割り当てられた環境で実施しています。
mysqlのドライバ取得$ go get -u github.com/go-sql-driver/mysqlフォルダ構成# GOPATHは「go env GOPATH」で確認 <GOPATH>/src/dbsample └ db.go想定するテーブルcreate table test_db.goods ( goods_id bigint not null auto_increment, name varchar( 255 ) not null, category varchar( 255 ), memo varchar( 255 ), index (goods_id), primary key (goods_id) ); insert into test_db.goods (name, category) values ("PC0001", "PC"); insert into test_db.goods (name, category) values ("PC0002", "PC"); insert into test_db.goods (name, category) values ("MB0001", "携帯電話"); insert into test_db.goods (name, category) values ("MB0002", "携帯電話");db.gopackage main import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" ) type Goods struct { goodsId int `db:goods_id` name string `db:name` category string `db:category` memo string `db:memo` } func main() { whereCategory := "PC" goodsRows := selectQuery(whereCategory) // *sql.Rows型からGoodsのスライスに詰め替えている。 var goodsSlice []Goods for goodsRows.Next() { var goods Goods goodsRows.Scan(&goods.goodsId, &goods.name, &goods.category, &goods.memo) goodsSlice = append(goodsSlice, goods) } fmt.Println(goodsSlice) } func selectQuery(whereCategory string) *sql.Rows { // DBへの操作は標準パッケージのdatabase/sqlを使用。 // DB接続(ホスト:192.168.10.10、DB:test_dbの場合) db, err := sql.Open("mysql", "master:master@tcp(192.168.10.10:3306)/test_db?charset=utf8mb4") if err != nil { panic(err.Error()) } defer db.Close() // ステートメント作成 query := "SELECT * FROM goods;" stmt, err := db.Prepare(query) if err != nil { panic(err.Error()) } defer stmt.Close() // クエリの実行 rows, err := stmt.Query() if err != nil { panic(err.Error()) } return rows }実行結果$ go run db.go [{1 PC0001 PC } {2 PC0002 PC }]標準パッケージのみでWebサーバー作成
標準パッケージで簡単なWebサーバーを作成してみます。
テンプレート機能もあります。簡単にですがコード例を紹介します。※以降はIPアドレス:192.168.10.10が割り当てられた環境で実施しています。
フォルダ構成sampleweb └ helloserver.go └ tmpl01.htmlhelloserver.gopackage main import ( "fmt" "html/template" "net/http" ) func main() { fmt.Println("Start Sample Server") server := http.Server{ Addr: ":8080", } http.HandleFunc("/", index) server.ListenAndServe() } func index(w http.ResponseWriter, r *http.Request) { t, err := template.ParseFiles("tmpl01.html") if err != nil { fmt.Println("template parse error.", err) } helloworldSlice := []string{"hello", "world"} err = t.Execute(w, helloworldSlice) if err != nil { fmt.Println("template execute error.", err) } }tmpl01.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Sample</title> </head> <body> <body> <ul> {{ range . }} <li>{{ . }}</li> {{ else }} データなし {{ end}} </ul> </body> </html>実行結果$ go run helloserver.go Start Sample Server (停止する場合はCtrl + C)別コンソールからcurlを叩いてみます。
実行結果$ curl 192.168.10.10:8080 <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Sample</title> </head> <body> <body> <ul> <li>hello</li> <li>world</li> </ul> </body> </html>ブラウザでアクセスすると以下のように表示される。
おわり
Golangはシンプルで構文も少ない方だと思いますが、まだまだ勉強することが多く奥が深いです。
着実にできることを増やしていきたいです。今回は以上です。
参考
The Go programming Language
Go言語.com
The Go Blog 日本語訳
プログラミング経験者がGo言語を本格的に勉強する前に読むための本
はじめてのGo言語
Go の並行処理
- 投稿日:2020-01-10T17:50:35+09:00
AWS CodePipelineでgoアプリのDockerイメージをECRに格納する
実現したいこと
- CodeCommitのリポジトリ(hoge-repo)のmasterブランチの更新をトリガーに
masterブランチのリソースからDockerイメージを作成しECRの対象リポジトリに格納したい- hoge-repoはgolangのアプリを持っているので,イメージ作成時にアプリのビルドも行う
環境
- Golang 1.13.5
- Docker 18
- AWS 東京リージョン
やったこと
CodeBuild構築
プロジェクト名は任意の名前
送信元
送信元はCodeCommitの対象リポジトリを選択今回はmasterマージをトリガーにするのでリファレンスタイプはブランチ
対象ブランチはmasterを指定
環境
Build環境は特に制限がなかったのでaws/codebuild/amazonlinux2-x86_64-standard:2.0を選択
残りの環境設定はデフォルトBuildspec
Buildspecファイルを使用するを選択
buildspec.yamlは後ほどアーティファクト
今回はDockerイメージのpushまでで,実際にEKSでコンテナの立ち上げまで行わないので
アーティファクトはなしを選択ログ
ログの監視も今回は行わないCodeBuildの権限追加
今回はCodeCommitからソースをダウンロードし,ECRにイメージプッシュするため
CodeBuildのサービスロールに以下ポリシーを付与
AWSCodeCommitReadOnlyAccess
AmazonEC2ContainerRegistryFullAccess
buildspec.yml作成
codebuildでデフォルト設定にしたので hoge-repoのルートディレクトリにbuildspec.ymlを配置
hoge-repo | |-- buildspec.yml |-- Makefile |-- Dockerfile |-- main.go ...buildspec.yaml
version: 0.2 phases: install: runtime-versions: docker: 18 commands: # ECRにイメージプッシュするためにログインする - $(aws ecr get-login --no-include-email --region ap-northeast-1) pre_build: commands: # go test -v -cover ./... - make test build: commands: # go build -v . - make build post_build: commands: # docker tag \code-commit-repo-name\:latest \erc-repo-name\:latest # docker push \erc-repo-name\:latest - make push-imageCodePipeline構築
CodePipelineの新規作成
パイプラインの設定
パイプライン名は任意の値を入力し,残りはデフォルトソースステージ
ソースプロバイダにAWS CodeCommitのhoge-repo masterブランチを選択
検索はAmazon CloudWatch Eventsを選択ビルドステージ
AWS CodeBuildの先ほど作成したプロジェクトを選択
今回環境変数は使用しないので残りはそのままデプロイステージ
実際にコンテナを立ち上げないのでデプロイステージはスキップする完成
ここまでの設定でCodeCommitのmasterブランチの更新をトリガーに
GoアプリがビルドされたDockerイメージを作成し,ECRにプッシュする構成ができた.この後
Pipeline実行する毎に goモジュール(外部ライブラリ)をダウンロードするのはイケてない(5分ぐらいかかる)
一度ダウンロードしたモジュールはキャッシュしておきたい(この後盛大にハマる)
- 投稿日:2020-01-10T17:43:23+09:00
GoでデバッグのためにgRPCのリクエストをトレースする
Go で gRPC が使われているライブラリを使っている時に、ライブラリが投げている実際の gRPC のリクエストをトレースしたい場合には Go の gRPC ライブラリが
golang.org/x/net/traceに対応している為、お手軽に確認する事ができる。grpc.EnableTracing = true go func() { http.ListenAndServe(":8080", nil) }()そのあと、
localhost:8080/debug/requestsを開けば以下のような出力が得られる。詳細は golang.org/x/net/trace を確認してもらえれば。
- 投稿日:2020-01-10T16:37:58+09:00
眺めて覚えるGo言語 その11 無名関数(Anonymous function)
では、眺め用サンプルコード
f11.gopackage main import ( "fmt" ) func main() { h1:=func(a string)(string){ return "<h1>"+a+"</h1>"} fmt.Println(h1("Topics")) } 実行結果 >go run f11.go <h1>Anonymous</h1>名無しの関数
f12.gopackage main import ( "fmt" ) func main() { func(){ for i:=0;i<10;i++{ fmt.Println("i=",i) } }() } 実行結果 >go run f12.go i= 0 i= 1 i= 2 i= 3 i= 4 i= 5 i= 6 i= 7 i= 8 i= 9
- 投稿日:2020-01-10T15:06:50+09:00
workaround for panic when defining the same flag in multiple packages and go test -coverpkg.
Issue
- go version: 1.13.5
複数のパッケージが同じ名前のフラグを定義している時、
go test -coverpkg=all ./...のように-coverpkgをつけてgo testすると、flag redefinedでpanicする問題。
- cmd/go: test coverpkg panics when defining the same flag in multiple packages · Issue #27336 · golang/go
- Go 1.13 にアップデートするとテスト時に "flag provided but not defined" エラーが発生するケース - blog.syfm
$ go test -v -cover -coverpkg=all ./... ? _/Users/y-goto/dev/go/flag-for-test/cmd1 [no test files] ? _/Users/y-goto/dev/go/flag-for-test/cmd2 [no test files] /var/folders/k7/2f4m6jwx6jjc2js45m62y9b00000gp/T/go-build687459203/b034/conf.test flag redefined: foo panic: /var/folders/k7/2f4m6jwx6jjc2js45m62y9b00000gp/T/go-build687459203/b034/conf.test flag redefined: fooTL;DR
とりあえず以下で避けられる。
init()でのフラグパースを避けるflag.NewFlagSetを使うHow to reproduce
- cmd1 - main.go // defines "foo" flag - cmd2 - main.go // defines "foo" flag - cfg - conf_test.go // no-op _test
cmd1/main.gopackage main import ( "flag" "fmt" ) var foo = flag.String("foo", "", "flag foo") func main() { flag.Parse() fmt.Println("foo", *foo) }
cmd2/main.gosame as
cmd1/main.go
cfg/conf_test.gopackage confgo test -coverpkg
$ go test -v -cover -coverpkg=all ./... ? _/Users/y-goto/dev/go/flag-for-test/cmd1 [no test files] ? _/Users/y-goto/dev/go/flag-for-test/cmd2 [no test files] /var/folders/k7/2f4m6jwx6jjc2js45m62y9b00000gp/T/go-build687459203/b034/conf.test flag redefined: foo panic: /var/folders/k7/2f4m6jwx6jjc2js45m62y9b00000gp/T/go-build687459203/b034/conf.test flag redefined: foo goroutine 1 [running]: flag.(*FlagSet).Var(0xc0000221e0, 0x11b58e0, 0xc0000523a0, 0x118c9d9, 0x3, 0x118d345, 0x8) /usr/local/go/src/flag/flag.go:848 +0x521 flag.(*FlagSet).StringVar(0xc0000221e0, 0xc0000523a0, 0x118c9d9, 0x3, 0x0, 0x0, 0x118d345, 0x8) /usr/local/go/src/flag/flag.go:751 +0x9e flag.(*FlagSet).String(...) /usr/local/go/src/flag/flag.go:764 flag.String(0x118c9d9, 0x3, 0x0, 0x0, 0x118d345, 0x8, 0xc000052390) /usr/local/go/src/flag/flag.go:771 +0xab _/Users/y-goto/dev/go/flag-for-test/cmd2.init() /Users/y-goto/dev/go/flag-for-test/cmd2/main.go:9 +0x53 FAIL _/Users/y-goto/dev/go/flag-for-test/conf 0.227s FAIL"flag redefined: foo" と怒られる。
Workaround
どうも
flag.CommandLine(デフォルトのFlagSet) に同名のFlagをセットしているとして怒られているっぽい(go testの初期化ロジックが変わった影響?)。そこで、各パッケージで個別のFlagSetを作ってそこにフラグをセットするように変更した。あと init() でフラグをパースしてもダメなのでmain()でやってください。package main import ( "flag" "fmt" "os" ) var ( command = flag.NewFlagSet(os.Args[0], flag.ExitOnError) foo = command.String("foo", "", "flag foo") ) func main() { command.Parse(os.Args[1:]) fmt.Println("foo", *foo) }
- 投稿日:2020-01-10T14:56:30+09:00
眺めて覚えるGo言語 その10 panic
Go言語のpanicを知ってパニクッタのである。
なんかエラーがあって継続するのに難しいときに使うのがpanicです。
f9.gopackage main import ( "fmt" ) func main() { defer fmt.Println("パニクリました。") fmt.Println("Start") panic("パニックになりました") } 実行結果 >go run f9.go Start パニクリました。 panic: パニックになりました goroutine 1 [running]: main.main() C:/Users/hirat/go-work/function/f9.go:10 +0x12b exit status 2上記のようにdeferと共に使うとプログラムの中断がうまく行く。
f10.gopackage main import ( "fmt" ) func helloworld() { defer fmt.Println("End") fmt.Println("Start") panic("Panic!") } func main() { helloworld() } 実行結果 >go run f10.go Start End panic: Panic! goroutine 1 [running]: main.helloworld() C:/Users/hirat/go-work/function/f10.go:10 +0x12b main.main() C:/Users/hirat/go-work/function/f10.go:15 +0x27 exit status 2関数の中でパニクッても大丈夫。
難しい言い方するとスタックポインターをちゃんと戻す。
- メモリーリークが起きない。
- 投稿日:2020-01-10T12:46:42+09:00
GoのDatetime忘れがちなので、まとめてみた
普段良く使うtimeメソッドをまとめてみた。
Formatについて
他の言語みたいな
YYYY-MM-DD hh:mm:sstimeフォーマット、Goでは↓このように書く
2006-01-02 15:04:05String -> Time
time.Parse(layout,string)を使って、文字列をTimeオブジェクトにする例:
str := "2020-01-09T19:00:00Z" t, _ := time.Parse("2006-01-02T15:04:05Z", str) // 出力結果: 2020-01-09 19:00:00 +0000 UTC時間の比較
After()メソッドを使う
t1 > t2なら、t1.After(t2) => true
t1 < t2なら、t1.After(t2) => falsestr1 := "2020/01/10 00:00:00" str2 := "2020/01/08 00:00:00" t1, _ := time.Parse("2006/01/02 15:04:05", str1) t2, _ := time.Parse("2006/01/02 15:04:05", str2) t1.After(t2)) // 出力結果:true2つ時刻の差を求める
Sub()メソッドは時間の経過値を求めるstart, _ := time.Parse("2006/01/02 15:04:05", "2020/01/08 00:00:00") end, _ := time.Parse("2006/01/02 15:04:05", "2020/01/10 00:00:00") duration := end.Sub(start) // 48h0m0s days := int(duration.Hours()) / 24 hours := int(duration.Hours()) % 24 mins := int(duration.Minutes()) % 60 secs := int(duration.Seconds()) % 60 log.Printf("%ddays + %dhours + %dminutes + %dseconds", days, hours, mins, secs) // 2days + 0hours + 0minutes + 0seconds現在の時間まで経過値を求める
Since()を使う、time.Now().Sub(t)の省略何日後、何日前の時間を求める
AddDate()を使う
引数には、year,month,dayを渡すnow := time.Now() // 2日後 after2days := now.AddDate(0, 0, 2) // 2020-01-12 11:56:27 // 2日前 before2days := now.AddDate(0, 0, -2) // 2020-01-08 11:57:49また、何分や何時間前や後は
Add()を使うと便利// 一時間後 t := now.Add(1 * time.Hour) // 30分後 t := now.Add(30 * time.Minute)
- 投稿日:2020-01-10T11:46:11+09:00
Goのmapのkeyを連結して平らにする
Code
package main import ( "fmt" ) type m map[string]interface{} var data = m{ "version": "1", "meta": m{ "id": "2", "user": m{ "id": "3", }, }, } func main() { FlattenMapKeyWithPeriod(data, "") } func FlattenMapKeyWithPeriod(data map[string]interface{}, prefix string) { for k, v := range data { d, ok := v.(m) if ok { FlattenMapKeyWithPeriod(d, prefix+k+".") } else { fmt.Printf("%s: %s\n", prefix+k, v.(string)) } } }In
{ "version": "1", "meta": { "id": "2", "user": { "id": "3", }, }, }Out
version: 1 meta.id: 2 meta.user.id: 3Playground
- 投稿日:2020-01-10T11:07:14+09:00
眺めて覚えるGo言語 その9 deferの書き方
deferは、遅延という意味だが分かりにくい。
眺めるための例題
f7.gopackage main import ( "fmt" "time" ) func main() { defer fmt.Println("全部終了しました。") for i:=0;i<10;i++{ time.Sleep(1) fmt.Println("i=",i) } } //実行結果 >go run f7.go i= 0 i= 1 i= 2 i= 3 i= 4 i= 5 i= 6 i= 7 i= 8 i= 9 全部終了しました。最初に書いた defer fmt.Println("全部終了しました。")は、
for以降のステートメント以降に遅延されたことがわかる。実際の場面
f8.gopackage main import ( "bufio" "fmt" "io" "os" ) func main() { fp, err := os.Open("d:\\00data\\test_utf20.csv") if err != nil {panic(err)} defer fp.Close() reader := bufio.NewReaderSize(fp, 4096) for { line, _, err := reader.ReadLine() fmt.Println(string(line)) if err == io.EOF { break } else if err != nil {panic(err)} } }上記のプログラムを眺めてみるとOpenしてdefer 付きのCloseをしている。
この表記は、もしファイルやio操作中に何らかのエラーが起きたときにcloseしてくれる。注意 皆さんは、エラーを起こすためにUsbFlashを引き抜かないでください。b壊れます。
- 投稿日:2020-01-10T10:31:50+09:00
ソースコードからdebパッケージを作成する(goの参考ビルド例付き)
TL;DR
- ソースコードからコンパイルするソフトウェアツールを,ubuntu 18.04などのdebパッケージにまとめる手順を説明します
- いわゆる野良ビルドなので,利用には注意が必要です
goのインストールを例に説明します手順
check-installコマンドを使用してdebパッケージを作ります.- ツールをソースからインストールする際に行う,いつもの手順
./configure && make && sudo make installの3番めのmake installの代わりに,checkinstallコマンドを使用するだけで,debパッケージを生成できます.- golangのソースコードにはMakefileが無いので,自分でMakefileを作ってしまいます
- Makefileには,
all,install,uninstallの3つのディレクティブの記載が必要ですcheck-installのインストール
aptitude isntall checkinstallgo用Makefileの作成
- 以下のコードをMakefileとして保存します.
- goをソースコードからビルドする手順を
allディレクティブに記述します- ビルド済みバイナリを(実行できるディレクトリに)コピーする手順を,
installディレクティブに記述します
- バージョンをディレクトリ名に追記しているので,
/usr/local/goディレクトリにシンボリックリンクを張ります- インストールしたファイル群を削除する手順を,
uninstallディレクティブに記述しますMakefile#!/bin/env make all: wget https://dl.google.com/go/go1.13.5.src.tar.gz wget https://dl.google.com/go/go1.4-bootstrap-20171003.tar.gz mkdir go1.13.5 && tar xzvf go1.13.5.src.tar.gz -C go1.13.5 --strip-components 1 mkdir go1.4-bootstrap && tar xzvf go1.4-bootstrap-20171003.tar.gz -C go1.4-bootstrap --strip-components 1 cd ./go1.4-bootstrap/src && \ CGO_ENABLED=0 bash ./make.bash && \ cd - cd ./go1.13.5/src && \ GOROOT_BOOTSTRAP=${PWD}/go1.4-bootstrap bash ./all.bash && \ cd - install: cp -r go1.13.5 /usr/local/go1.13.5 ln -s /usr/local/go1.13.5 /usr/local/go uninstall: rm -rf /usr/local/go1.13.5 /usr/local/go clean: rm -f go1.13.5.src.tar.gz go1.4-bootstrap-20171003.tar.gz rm -rf go1.13.5 go1.4-bootstrapdebパッケージの作成
make installの代わりに,checkinstallコマンドを実行します
--install=noオプションを使用しインストール作業は行いません- インストールは生成されたdebパッケージを使用します
- checkinstall時に出てくる質問には基本的にはすべてエンターキーでデフォルト値を選択します
sudo checkinstall --install=no # 質問には基本的にはすべてエンターキーでデフォルト値を選択する # go_20191227-1_amd64.debパッケージのインストール&アンインストール
### インストール dpkg -i go_20191227-1_amd64.deb ### 確認作業 export PATH=/usr/local/go/bin:${PATH} which go # /usr/local/go/bin/go go version # go version go1.13.5 linux/amd64 ### アンインストール dpkg -r go参考
- 投稿日:2020-01-10T10:27:21+09:00
golangをソースコードからインストールする(CentOS/Ubuntu)
TL;DR
- Go言語のプログラムのコンパイラ+実況環境(
goコマンド)をソースコードからインストールする方法を紹介しますGo言語のビルド手順
Installing Go from sourceの説明をもとにインストールします.
直近のバージョンのgoは,goでビルドします.そのため,(以前のバージョン)
go1.4をいったんビルドしてから,最近のバージョンのgoをビルドする手順を取ります
(もとの説明は「Bootstrap toolchain from source」にあります).手順は以下のとおりです.使用したいgoを1.13.5(2019年12月27日時点)とします.
- go1.4をダウンロード,ビルドする
- go1.13.5をダウンロード,go1.4のディレクトリを指定してビルドする
go1.4のビルド
- まず最初に,goを使用しないでビルドできるgo1.4をダウンロードし,ビルドします
wget https://dl.google.com/go/go1.4-bootstrap-20171003.tar.gz mkdir go1.4-bootstrap && tar xzvf go1.4-bootstrap-20171003.tar.gz -C go1.4-bootstrap --strip-components 1 cd ./go1.4-bootstrap/src CGO_ENABLED=0 bash ./make.bash cd -
- go1.4-bootstrap-20171003.tar.gzをダウンロードします
- 展開後のディレクトリ名が
goで,以降のgo1.13.5とバッティングしてしまうので,go1.4-bootstrapという名前のディレクトリに展開しますmake ./make.bashで,最小限のビルドを行いますgo1.4-bootstrap/binにgo,gofmtが生成されますgo1.13.5のビルド
wget https://dl.google.com/go/go1.13.5.src.tar.gz mkdir go1.13.5 && tar xzvf go1.13.5.src.tar.gz -C go1.13.5 --strip-components 1 cd ./go1.13.5/src GOROOT_BOOTSTRAP=${PWD}/go1.4-bootstrap bash ./all.bash cd -
- go1.13.5.src.tar.gzをダウンロードします
- 特段ディレクトリ名を変更する必要はないのですが,他のディレクトリと混在しないようにバージョン名を含めた名前で展開します
go1.13.5/srcディレクトリに移動して,ビルドします.このとき環境変数GOROOT_BOOTSTRAPに,前章でビルドしたgo1.4のディレクトリを指定しますgo1.13.5/binにgo,gofmtが生成されますビルドしたgo1.13.5ディレクトリを実行パスに移動
ビルドしたgo1.13.5ディレクトリをわかりやすい場所に移動します.
mv ./go1.13.5 /usr/local/ ln -s /usr/local/go1.13.5 /usr/local/goテスト
/usr/local/go/bin/goが使用できることを確認します.export PATH=/usr/local/go/bin:${PATH} which go # /usr/local/go/bin/go go version # go version go1.13.5 linux/amd64参考
- 投稿日:2020-01-10T10:15:59+09:00
眺めて覚えるGo言語 その8 関数の書き方
一般的な関数の書き方
f1.gopackage main func add(a,b int)int{ return a+b } func main(){ print(add(10,20)) } 実行 >go run f1.go 30- a,b intは、引数です。intは、引数の型を表します。
- その後ろに書いてあるintは、戻り値の型を示します。慣れてきたらnamed return value(naked return)を使ってみよう
f2.gopackage main func fun(a ,b int)(x,y,z int){ x=a*10 y=b*20 z=a+b return } func main(){ a,b,c:=fun(10,20) println(a,b,c) } 実行 >go run f2.go 100 400 30
- あらかじめ戻り値を定義することによりreturn一発で戻ることができる。(x,y,z int)
f3.gopackage main func fun(a ,b int)(x int,err string){ if (b==0) { err="ゼロでで割るな" return } x=a/b return } func main(){ for i:=3;i>=0;i-- { a,err:=fun(10,i) println(i,a,err) } } 実行 >go run f3.go 3 3 2 5 1 10 0 0 ゼロでで割るなGo言語には、例外処理がない。あらかじめ考慮してプログラムする必要がある。
OSやコンパイラ頼みのアプリケーションは、運用側のエンジニアに多大な迷惑がかかる。
- 投稿日:2020-01-10T10:09:22+09:00
Serverless Framework for AWS Lambda Development
0 .Intro
The complete solution for building & operating serverless applications.
就自己這陣子的使用經驗上來看,我覺得這樣的敘述還算名符其實。
目前最主要會用場景為開發在自己的MBPR 上利用Golang 開發AWS Lambda,利用Serverless Framework CLI所提供的功能來開發,部署以及在本機做測試,整體的流暢度滿高的。 雖然Serverless還有提供其他Monorting, integration或policy目前還沒有使用到,但是整理的使用經驗大勝SAM CLI,特別是早期被SAM對於Golang的支援踩到太多的坑了。
除了AWS之外,其他serverless的部署對象目前有都有支援。更多完整可以參考Serverless Framework
真的要說目前Serverless Framework有什麼缺點,我覺得是名字取得不好XD 造成要SEO/search的結果不佳。
1. Perquisite & Installation
在開始之前,要先確認你的電腦椅已經設定好AWS CLI相關的configuration,確認沒有問題後,我們就可以來安裝Serverless Framework。
# install serverless cli curl -o- -L https://slss.io/install | bash # testing sls --version # upgrade to latest version sls upgrade2. Create New Lambda Service
接著,我們可以透過 Serverless framework 提供的 template 來完成,
-t參數代表template,目前這邊我們選用的是採用 go mod 的 template,如果你的專用目前是用go dep來管理的話,你也可以選用 aws-go-dep 這個template來建立 hello world。
-p代表的是Path,command執行完成後,Serverless framework會幫我們將產生的檔案放在新建立的myService資料夾中。sls create -t aws-go -p myService cd myService切換到myService下後,你可以看到我們從template產生的兩個lambda的範例,一個hello一個world分別放在不同的資料夾中,並且可以稍微瀏覽一下兩隻lambda裡面的code,分別會回傳不同的hello world訊息。
3. Build & Deploy
Serverless Framework很貼心的已經幫我們處理好Makefile,因此我們需要先init go mod,接著透過make build的command來幫我們產生binary。
#/myService go mod init helloworld make build確認
/bin資料夾中有成功產生hello跟world兩個檔案後,在deploy之前有個小小需要注意的地方,如果你目前的aws config中的region不是us-east-1的話,記得要修改serverless.yml中region的設定。# serverless.yml region: <YOUR AWS CONFIG REGION> sls deploy4. Testing Your Lambda
部署完成後,我們可以分別來測試這兩隻不同的lambda,
-f代表要執行的function。
- lambda hello
sls invoke -f hello { "message": "Go Serverless v1.0! Your function executed successfully!" }
- lambda world
sls invoke -f world { "message": "Okay so your other function also executed successfully!" }有興趣的人可以進一步登入到AWS web console中,便可以發現Serverless Framwork在剛剛一行command中,已經幫我們部署了API Gateway+lambda,而且這些設定指示在serverless.yml中functions > hello > events > http的config便可以完成,其他我們在lambda中常用的Trigger,在被註解掉的event sample中也都已經給了範例。
Hello World的介紹屆先到這邊,之後的篇章再來分享透過Serverless Framework實作的其他lambda。
- 投稿日:2020-01-10T01:19:44+09:00
DB接続付きGraphQLサーバ(by Golang)をローカルマシン上でDockerコンテナ起動
お題
表題の通り。前回までGraphQLを題材にフロント・バックエンドそれぞれで実装を進めてきた。
まだまだ実装することは山ほどあるけど、今のところローカルマシン内でほそぼそと立ち上げているこのアプリをGKEにでも載せてみようと思っているので、まず手始めにアプリのDocker化を試みる。
今回は、バックエンド(Golang)だけ。接続するDBはローカルのDockerコンテナのまま。関連記事索引
- 第5回「DB接続付きGraphQLサーバ(by Golang)をローカルマシン上でDockerコンテナ起動」
- 第4回「graphql-codegenでフロントエンドをGraphQLスキーマファースト」
- 第3回「go+gqlgenでGraphQLサーバを作る(GORM使ってDB接続)」
- 第2回「NuxtJS(with Apollo)のTypeScript対応」
- 第1回「frontendに「nuxtjs/apollo」、backendに「go+gqlgen」の組み合わせでGraphQLサービスを作る」
開発環境
# OS - Linux(Ubuntu)
$ cat /etc/os-release NAME="Ubuntu" VERSION="18.04.2 LTS (Bionic Beaver)"# バックエンド
言語 - Go
$ go version go version go1.13.3 linux/amd64パッケージマネージャ - Go Modules
IDE - Goland
GoLand 2019.3.1 Build #GO-193.5662.65, built on December 23, 2019# Dockerコンテナ
Docker
$ $ sudo docker -v Docker version 19.03.5, build 633a0ea838docker-compose
$ docker-compose -v docker-compose version 1.23.1, build b02f1306実践
今回の全ソースは下記。
https://github.com/sky0621/study-graphql/tree/v0.5.0プロジェクト構成
$ pwd /home/sky0621/src/github.com/sky0621/study-graphql $ $ tree -L 1 . ├── backend ├── docker-compose.yml ├── Dockerfile ├── frontend ├── persistence ├── README.md └── schema今回の記事で絡むのは、上記のうち「
backend」配下のGolangソースと「Dockerfile」に「docker-compose.yml」。
あとは、MySQLコンテナ起動時にアプリが必要とするテーブルを作るため「persistence」配下のDDL。Dockerfile
何はともあれDockerfile。
マルチステージビルドで実稼働コンテナは超軽量なscratchベースに。
以下、流用しつつ。
https://qiita.com/sky0621/items/4c314bd07da284176a29#dockerfile[Dockerfile]# step 1: build go app FROM golang:1.13.5-alpine3.11 as build-step # for go mod download RUN apk add --update --no-cache ca-certificates git RUN mkdir /go-app WORKDIR /go-app COPY backend/go.mod . COPY backend/go.sum . RUN go mod download COPY backend . RUN cd server && CGO_ENABLED=0 go build -o /go/bin/go-app # ----------------------------------------------------------------------------- # step 2: exec FROM scratch COPY --from=build-step /go/bin/go-app /go/bin/go-app EXPOSE 5050 ENTRYPOINT ["/go/bin/go-app"]Goの
main関数があるファイルは「backend/server/main.go」にあるため、このファイルがビルド対象。
なので、RUN cd serverに続けてgo buildしてる。
あと、GoのアプリはGraphQLサーバとして実装しているのだけど、ポートを5050で起動しているので、EXPOSE 5050と書いた。
ただ、↓によると「これだけではホストからコンテナにアクセスできるようにしません。」なんだとか。
http://docs.docker.jp/engine/reference/builder.html#exposedocker-compose.yml
続いて、ローカルでもろもろのDockerコンテナをひとまとめに制御するときに便利なドッカーコンポーズ。
[docker-compose.yml]version: '3' services: db: restart: always image: mysql:5.7.24 command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci ports: - "3306:3306" environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_USER: localuser MYSQL_PASSWORD: localpass MYSQL_DATABASE: localdb volumes: - ./persistence/init:/docker-entrypoint-initdb.d networks: - study-graphql-network app: build: . ports: - "80:5050" networks: - study-graphql-network volumes: localdb: external: false networks: study-graphql-network: external: truedbサービス
1つ目のサービス定義の「
db」は見たまんま、MySQLコンテナを立ち上げるためのもの。これはまあ特に言及することない。定義した情報でDBができる。
ちなみに、以下のようにDDLが用意してあってコンテナ起動時にこのDDLが叩かれてテーブルも作られる。$ tree persistence/ persistence/ ├── init │ └── 1_create.sql └── README.md $ $ cat persistence/init/1_create.sql CREATE TABLE IF NOT EXISTS `todo` ( `id` varchar(64) NOT NULL, `text` varchar(256) NOT NULL, `done` bool NOT NULL, `user_id` varchar(64) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; CREATE TABLE IF NOT EXISTS `user` ( `id` varchar(64) NOT NULL, `name` varchar(256) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin;appサービス
2つ目のサービス定義の「
app」は、同一階層にあるDockerfileを使ってGoアプリをビルドし、以下の指定によってホストマシンの 80 番ポートでコンテナ内のアプリにアクセス可能にする。ports: - "80:5050"networks
今回、GoアプリからMySQLデータベース内のテーブルにアクセスする実装にしているのでコンテナ間で通信できないと意味がない。
Dockerでは明示的にネットワークを作成してコンテナ間で同じネットワーク内ということを実現できるようなので、そのように指定。まずは、ネットワークを作る。
$ sudo docker network ls NETWORK ID NAME DRIVER SCOPE 1abb47a7ebd9 bridge bridge local 0843ed48f717 host host local 09614bd65e4d none null local $ $ sudo docker network create study-graphql-networkとすると、
$ sudo docker network ls NETWORK ID NAME DRIVER SCOPE 1abb47a7ebd9 bridge bridge local 0843ed48f717 host host local 09614bd65e4d none null local ba8a943cb12b study-graphql-network bridge localといった感じで新たなネットワークが作られるので、あとは
docker-compose.yml内で明示的に使う指定をするだけ。services: db: 〜〜: networks: - study-graphql-network app: 〜〜: networks: - study-graphql-network networks: study-graphql-network: external: trueGoアプリのmain関数
GoアプリはGraphQLサーバとして起動するのだけど、起動時に指定したデータソース接続文字列によってDB接続に行く。
以下の部分なのだけど、ここで指定しているホストIPは、実はちゃんと調べたもの。
「dataSource = "localuser:localpass@tcp(172.19.0.1:3306)/localdb?」前段で作っておいたDockerネットワーク、こいつの情報を以下のように表示すると、そこにこのネットワークのゲートウェイIPが載ってるので、それを記載。
$ sudo docker network inspect study-graphql-network [ { "Name": "study-graphql-network", "Id": "ba8a943cb12b07e6dcddbc421a5d5b63c8f48cf526bf1da3ec454bad26233fad", "Created": "2020-01-07T08:54:36.947913642+09:00", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": {}, "Config": [ { "Subnet": "172.19.0.0/16", "Gateway": "172.19.0.1" } ] }, "Internal": false, "Attachable": false, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": {}, "Options": {}, "Labels": {} } ]以下、一応 main 関数を含むソースを記載。(このファイル分だけ載せてもしょうがないのだけど。)
[backend/server/server.go]package main import ( "log" "net/http" "os" "github.com/99designs/gqlgen/handler" "github.com/jinzhu/gorm" "github.com/sky0621/study-graphql/backend" _ "github.com/go-sql-driver/mysql" ) const dataSource = "localuser:localpass@tcp(172.19.0.1:3306)/localdb?charset=utf8&parseTime=True&loc=Local" const defaultPort = "5050" func main() { port := os.Getenv("PORT") if port == "" { port = defaultPort } db, err := gorm.Open("mysql", dataSource) if err != nil { panic(err) } if db == nil { panic(err) } defer func() { if db != nil { if err := db.Close(); err != nil { panic(err) } } }() db.LogMode(true) http.Handle("/", handler.Playground("GraphQL playground", "/query")) http.Handle("/query", handler.GraphQL(backend.NewExecutableSchema(backend.Config{Resolvers: &backend.Resolver{DB: db}}))) log.Printf("connect to http://localhost:%s/ for GraphQL playground", port) log.Fatal(http.ListenAndServe(":"+port, nil)) }ビルド
せっかくdocker-compose使ってるので、
docker-compose build
初回はそれなりに時間がかかる。(2回目以降はキャッシュが効いて、とても早い。)$ sudo docker-compose build db uses an image, skipping Building app Step 1/13 : FROM golang:1.13.5-alpine3.11 as build-step 1.13.5-alpine3.11: Pulling from library/golang e6b0cf9c0882: Pull complete 2848faf0eed1: Pull complete 〜〜省略〜〜 Step 13/13 : ENTRYPOINT ["/go/bin/go-app"] ---> Running in ab52efb17aa9 Removing intermediate container ab52efb17aa9 ---> 10ab8cd2ef9e Successfully built 10ab8cd2ef9e Successfully tagged study-graphql_app:latestDocker起動
$ sudo docker-compose up Starting study-graphql_db_1_7a805d40cf64 ... done Starting study-graphql_app_1_3cd84c32df8a ... done Attaching to study-graphql_db_1_7a805d40cf64, study-graphql_app_1_3cd84c32df8a db_1_7a805d40cf64 | 2020-01-09T16:10:28.082404Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details). db_1_7a805d40cf64 | 2020-01-09T16:10:28.084061Z 0 [Note] mysqld (mysqld 5.7.24) starting as process 1 ... app_1_3cd84c32df8a | 2020/01/09 16:10:28 connect to http://localhost:5050/ for GraphQL playground db_1_7a805d40cf64 | 2020-01-09T16:10:28.088730Z 0 [Note] InnoDB: PUNCH HOLE support available db_1_7a805d40cf64 | 2020-01-09T16:10:28.088753Z 0 [Note] InnoDB: Mutexes and rw_locks use GCC atomic builtins 〜〜〜 省略 〜〜〜 db_1_7a805d40cf64 | 2020-01-09T16:10:28.342594Z 0 [Note] Event Scheduler: Loaded 0 events db_1_7a805d40cf64 | 2020-01-09T16:10:28.345471Z 0 [Note] mysqld: ready for connections. db_1_7a805d40cf64 | Version: '5.7.24' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL)動作確認
GraphQLの挙動を確認するため、公開した
80番ポートにアクセス。
こんな感じでGraphQLのプレイグラウンドが表示される。試しに、 todo テーブルにレコードを追加する mutation を実行してみる。(左が実行した mutation で右が実行結果)
まとめ
次回は、フロントエンド(Vue.js/Nuxt.js)のDocker化かなぁ。
いっそのことGKE載せちゃおうかな。。。





