20200110のGoに関する記事は14件です。

Golang入門記事の没ネタサンプルコード

Golang入門中です。

以前、以下の記事を書きました。
Golang入門:AtCoderなどの競技プログラミング問題を解き始めるための基本構文+その他おまけTips

記事に載せようかと思い書いてみたけれど、
趣旨から外れるため除外したサンプルコードをせっかくなのでまとめて公開したいと思います。

入門者によるざっくり試してみたコードの羅列ですが、参考になれば幸いです。

型宣言

基本の型から独自の型を宣言できます。

newType01.go
package 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.go
package 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.go
package 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.go
package 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 syntax

typeは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.go
package 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.go
package 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.go
package 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 main

Channel

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.go
package 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.html
helloserver.go
package 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>

ブラウザでアクセスすると以下のように表示される。

2020-01-10_10h06_23.png

おわり

Golangはシンプルで構文も少ない方だと思いますが、まだまだ勉強することが多く奥が深いです。
着実にできることを増やしていきたいです。

今回は以上です。

参考

The Go programming Language
Go言語.com
The Go Blog 日本語訳
プログラミング経験者がGo言語を本格的に勉強する前に読むための本
はじめてのGo言語
Go の並行処理

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS CodePipelineでgoアプリのDockerイメージをECRに格納する

実現したいこと

  1. CodeCommitのリポジトリ(hoge-repo)のmasterブランチの更新をトリガーに
    masterブランチのリソースからDockerイメージを作成しECRの対象リポジトリに格納したい
  2. hoge-repoはgolangのアプリを持っているので,イメージ作成時にアプリのビルドも行う

環境

  • Golang 1.13.5
  • Docker 18
  • AWS 東京リージョン

やったこと

CodeBuild構築

  • プロジェクト名は任意の名前

  • 送信元

    送信元はCodeCommitの対象リポジトリを選択

スクリーンショット 2020-01-10 15.53.22.png

今回は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-image

CodePipeline構築

CodePipelineの新規作成

  • パイプラインの設定
    パイプライン名は任意の値を入力し,残りはデフォルト

  • ソースステージ
    ソースプロバイダにAWS CodeCommitのhoge-repo masterブランチを選択
    検索はAmazon CloudWatch Eventsを選択

  • ビルドステージ
    AWS CodeBuildの先ほど作成したプロジェクトを選択
    今回環境変数は使用しないので残りはそのまま

  • デプロイステージ
    実際にコンテナを立ち上げないのでデプロイステージはスキップする

完成

ここまでの設定でCodeCommitのmasterブランチの更新をトリガーに
GoアプリがビルドされたDockerイメージを作成し,ECRにプッシュする構成ができた.

この後

Pipeline実行する毎に goモジュール(外部ライブラリ)をダウンロードするのはイケてない(5分ぐらいかかる)
一度ダウンロードしたモジュールはキャッシュしておきたい(この後盛大にハマる)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 を開けば以下のような出力が得られる。

スクリーンショット 0002-01-10 17.35.25.png

詳細は golang.org/x/net/trace を確認してもらえれば。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

眺めて覚えるGo言語 その11 無名関数(Anonymous function)

では、眺め用サンプルコード

f11.go
package 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.go
package 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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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する問題。

$ 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

TL;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.go

package main

import (
        "flag"
        "fmt"
)

var foo = flag.String("foo", "", "flag foo")

func main() {
        flag.Parse()
        fmt.Println("foo", *foo)
}

cmd2/main.go

same as cmd1/main.go

cfg/conf_test.go

package conf

go 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)
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

眺めて覚えるGo言語 その10 panic

Go言語のpanicを知ってパニクッタのである。

なんかエラーがあって継続するのに難しいときに使うのがpanicです。

f9.go
package 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.go
package 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

関数の中でパニクッても大丈夫。

難しい言い方するとスタックポインターをちゃんと戻す。

  • メモリーリークが起きない。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GoのDatetime忘れがちなので、まとめてみた

普段良く使うtimeメソッドをまとめてみた。

Formatについて

他の言語みたいなYYYY-MM-DD hh:mm:sstimeフォーマット、Goでは↓このように書く
2006-01-02 15:04:05

String -> 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) => false

    str1 := "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)) // 出力結果:true

2つ時刻の差を求める

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)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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: 3

Playground

https://play.golang.org/p/du0d8_ZvyKX

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

眺めて覚えるGo言語 その9 deferの書き方

deferは、遅延という意味だが分かりにくい。

眺めるための例題

f7.go
package 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.go
package 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壊れます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ソースコードから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には,allinstalluninstallの3つのディレクティブの記載が必要です

check-installのインストール

aptitude isntall checkinstall

go用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-bootstrap

debパッケージの作成

  • 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

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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日時点)とします.

  1. go1.4をダウンロード,ビルドする
  2. 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/bingogofmtが生成されます

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/bingogofmtが生成されます

ビルドした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

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

眺めて覚えるGo言語 その8 関数の書き方

一般的な関数の書き方

f1.go
package 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.go
package 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.go
package 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やコンパイラ頼みのアプリケーションは、運用側のエンジニアに多大な迷惑がかかる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Serverless Framework for AWS Lambda Development

0 .Intro

Serverless Framework

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 upgrade 

2. 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 deploy

4. Testing Your Lambda

部署完成後,我們可以分別來測試這兩隻不同的lambda,-f代表要執行的function。

  1. lambda hello
    sls invoke -f hello


    {
        "message": "Go Serverless v1.0! Your function executed successfully!"
    }
  1. 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。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DB接続付きGraphQLサーバ(by Golang)をローカルマシン上でDockerコンテナ起動

お題

表題の通り。前回までGraphQLを題材にフロント・バックエンドそれぞれで実装を進めてきた。
まだまだ実装することは山ほどあるけど、今のところローカルマシン内でほそぼそと立ち上げているこのアプリをGKEにでも載せてみようと思っているので、まず手始めにアプリのDocker化を試みる。
今回は、バックエンド(Golang)だけ。接続するDBはローカルのDockerコンテナのまま。

関連記事索引

開発環境

# 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 633a0ea838

docker-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#expose

docker-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: true

dbサービス

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: true

Goアプリの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:latest

Docker起動

$ 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 番ポートにアクセス。
Screenshot from 2020-01-10 01-14-13.png
こんな感じでGraphQLのプレイグラウンドが表示される。

試しに、 todo テーブルにレコードを追加する mutation を実行してみる。(左が実行した mutation で右が実行結果)
Screenshot from 2020-01-10 01-16-32.png

登録した結果も query で表示してみる。
Screenshot from 2020-01-10 01-18-44.png

まとめ

次回は、フロントエンド(Vue.js/Nuxt.js)のDocker化かなぁ。
いっそのことGKE載せちゃおうかな。。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む