マイクロサービスやってますか?
私は今イギリスの会社でソフトウェアエンジニアとして日々マイクロサービス開発をしています。
イギリスで働くことになった経緯などはこちらに書き残したのでもし興味があればのぞいてみてください。
弊社では3年前からKubernetesを使ったマイクロサービス開発を行なっており、本番で運用するための知見が多くあります。今回、アドベントカレンダーというイベントを通して海外でのマイクロサービス開発の一事例を紹介していけたらなと思います。
まっさらな状態から徐々にサービスを育てていく形式で進めていこうと思います。
ノリで区切っていくので25日より短いかもしれないし、25日では足りないかもしれません。笑
どうか温かく見守っていただければと思います。
アプリケーションのレポジトリはこちらです。
ステップごとにコミットしていくのでバージョンを切り替えながら見てください。
では早速サービスを作っていきましょう。
リポジトリ作成
クリーンなリポジトリを作成したら以下の作業をします。
go mod init
- main.goファイルの作成
go build
でバイナリが作成できるか試してみましょう。
Hello, world
今回は jawher/mow.cli ライブラリを使ってアプリを書いていきます。
シンプルで使いやすいためオススメです。
Hello, world
を出力するコードはこんな感じになります。
package main
import (
cli "github.com/jawher/mow.cli"
"log"
"os"
)
const (
appName = "qiita-advent-calendar-2019"
appDesc = "The micro service sample app"
)
func main() {
app := cli.App(appName, appDesc)
app.Action = func() {
log.Println("Hello, world")
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
早速ビルドして走らせてみましょう。
$ go build
$ ./qiita-advent-calendar-2019
2019/11/14 14:44:24 Hello, world
バイナリにgithashの値を埋め込む
githashをコンパイル時に埋め込み、コード内からアクセスできるようにしておきます。何か問題が発生した際などにどのバージョンのアプリなのかすぐわかるようできるなど便利なことがあります。
ついでに sirupsen/logrus ライブラリを使ってログをいい感じにします。
package main
import (
"os"
cli "github.com/jawher/mow.cli"
log "github.com/sirupsen/logrus"
)
var gitHash = "overriden at compile time"
const (
appName = "qiita-advent-calendar-2019"
appDesc = "The micro service sample app"
)
func main() {
app := cli.App(appName, appDesc)
app.Action = func() {
log.WithField("git_hash", gitHash).Println("Hello, world")
}
if err := app.Run(os.Args); err != nil {
log.WithError(err).Fatal("app run")
}
}
ビルドコマンドはgithashを埋め込む記述が入るためこうなります。
$ go build -ldflags "-X main.gitHash=$(git rev-parse HEAD)"
$ ./qiita-advent-calendar-2019
INFO[0000] Hello, world git_hash=2e94c00b197a3b1e131c09ce56545e56527a1ba0
logrusライブラリでは出力形式をjsonにすることもできますし、ログレベルの設定も簡単です。
サーバープロセスの開始
では、サーバープロセスを走らせてみましょう。
新しいプロセスはgoルーティンで開始し、メインのルーティンでは子ルーティンからのエラーメッセージを待ち受ける形で同期します。
- プロセスの立ち上げ
errCh := make(chan error, 1)
go func() {
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, world")
})
if err := http.ListenAndServe(net.JoinHostPort("", strconv.Itoa(*srvPort)), nil); err != nil {
errCh <- errors.Wrap(err, "server")
}
}()
- 子ルーティンの待ち受け
select {
case err := <-errCh:
log.Println(err)
}
なお、サーバのポート番号は変更可能なように環境変数として受け取れるようにします。mow.cliライブラリではこんな感じに記述します。
srvPort := app.Int(cli.IntOpt{
Name: "srv-port",
Desc: "http server port",
EnvVar: "SRV_PORT",
Value: 8080,
})
では、実行してcurlしてみましょう。
$ go build -ldflags "-X main.gitHash=$(git rev-parse HEAD)"
$ ./qiita-advent-calendar-2019
INFO[0000] Hello, world git_hash=86bd5b5d84ec8b02329d80e62c5c76ecf2ded099
$ curl localhost:8080/bar
Hello, world
さいごに
シグナルを受けとった際にも安全に終了できるように、select句で同時に待ち受けておきましょう。
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
select {
case err := <-errCh:
log.Println(err)
case <-sigCh:
log.Println("termination signal received. attempt graceful shutdown")
}
$ ./qiita-advent-calendar-2019
INFO[0000] Hello, world git_hash=d2cf7e593de77bc49a98a76fd1ee25f49bdaf795
^CINFO[0004] termination signal received. attempt graceful shutdown
INFO[0004] bye
明日は、アプリケーションをデプロイしていきたいと思います。