InfoQの「Build Your Own Container Using Less than 100 Lines of
Go」という記事を読んで、コンテナが技術的にどのように成り立っているのか、大変面白く学ぶことができました。本ブログでは、その記事の内容を紹介していきます。
Build Your Own Container Using Less than 100 Lines of GoShipping containers and
software containers share a lot in common, but the analogy has limits. This
article explores this relationship further by demonstrating how it is possible
to build a simple container using less than 100 lines of Golang code. Topics
covered include namespaces, cgroups and laye…Infはじめに
Goでecho [https://github.com/labstack/echo]を使って以下のようなサーバーを作っていました。
var db *sql.DB
func main() {
e := echo.New()
e.POST("/", func(c echo.Context) error {
ctx := c.Request().Context()
// 自身の管理するDBに保存する
db.ExecContext(ctx, "INSERT INTO ...")
// 別のサーバーで管理するDBに保存する
req, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
"http://localhost:8081/",
nil,
)
if err != nil {
return err
}
res, err := http.DefaultClient.Do(req)
...
return c.NoContent(http使用しているライブラリの外部通信の中身(リクエストのヘッダやレスポンスのステータス等)をデバッグのために出力したくなったのでまとめました。
方法
以下のように外部のサーバーにリクエストするようなコードがあったとします。
package main
import (
"context"
"fmt"
"net/http"
)
func main() {
if err := run(); err != nil {
panic(err)
}
}
var c = &http.Client{}
func run() error {
ctx := context.Background()
req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
"http://localhost:8080",
nil,
)
if err != nil {
return err
}
res, err := c.Do(req)
if err != nil {
return err
}
fmtGo1.18から導入された型パラメータ comparable は、 == や != で比較可能な型のみを受け入れます。
mapのキーにジェネリックな型を指定するような関数でよく使うと思います。
func ToMap[T comparable](ss []T) map[T]struct{} {
m := make(map[T]struct{}, len(ss))
for _, s := range ss {
m[s] = struct{}{}
}
return m
}
int や string などの組み込み型はもちろん、構造体も comparable を満たします。
type User struct {
ID int
Name string
}
func main() {
us := []User{
{
ID: 1,
Name: "1",
},
{
ID: 2,
Name: "2",
},
}
m := ToMap(us)
_, ok := m[User{ID: 1, Name: "1"}]
fmはじめに
GoでRedisを使ったキャッシュを実装する際、同じような処理を何度も書くことになりそうだったので共通化しました。
実装
Redisへリクエストする処理をinterfaceで定義しておきます。
type Client interface {
Get(context.Context, string) ([]byte, bool, error)
Set(context.Context, string, []byte, time.Duration) error
}
このinterfaceを満たすclientを実装します。Redisへの接続には github.com/go-redis/redis
[https://pkg.go.dev/github.com/go-redis/redis/v8@v8.11.5] を使いました。
type client struct {
c *redis.Client
}
func NewClient(addr, pass string) cache.Client {
c := redis.NewClient(
&redis.OpPhoto by Markus Spiske
[https://unsplash.com/@markusspiske?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit]
/ Unsplash
[https://unsplash.com/?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit]
※go version go1.19 darwin/amd64
ファイルを作成するGoの関数os.OpenFileは、3つの引数を取ります。
https://pkg.go.dev/os#OpenFile
func OpenFile(name string, flag int, perm FileMode) (*File, error)
第2引数のflag intは以下のような値を入れられます
os.OpenFile("filename.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
この縦棒でつないはじめに
Goの標準パッケージの context.Context は context.WithValue 関数を使って任意の値を格納することができます。
context.WithValue には以下のようなコメントが書かれています。
// WithValue returns a copy of parent in which the value associated with key is
// val.
//
// Use context Values only for request-scoped data that transits processes and
// APIs, not for passing optional parameters to functions.
//
// The provided key must be comparable and should not be of type
// string or any other built-in type to avoid collisions between
// packages using csliceの構造
Goのsliceは 配列へのポインタ 長さ 容量 を持った構造体として定義されています。
go/slice.go at go1.18 · golang/goThe Go programming language. Contribute to
golang/go development by creating an account on GitHub.GitHubgolang
[https://github.com/golang/go/blob/go1.18/src/runtime/slice.go#L15-L19]type slice struct {
array unsafe.Pointer
len int
cap int
}
sliceの構造については以下の記事が分かりやすかったです。
実装して理解するスライス #golang - Qiitaはじめに この記事はGoアドベントカレンダーの1日目の記事です。 スライスの実態
runtimeのコードをみるとGoのスライスは以下のように定義されています。 type slice struct { arr
interfaceのnilチェックをしているのにメソッド呼び出し時にpanicが起きることがあり、その時に調べたことのメモです。
該当コードを簡略化して書くと以下のような感じです。
package main
import "fmt"
type Message interface {
Str() string
}
var _ Message = &Text{}
type Text struct {
text string
}
func (t *Text) Str() string {
return t.text
}
func NewText(str string) *Text {
if str == "" {
return nil
}
return &Text{
text: str,
}
}
NewText() は引数が空文字の場合にnilを返します。
これに対して、以下のような Print() 関数を用意して実行すると、
func Print(m Message) {
if m != nil {
fmt.Println(m.Str
自社のサービスではOpenAPIを使ってサーバーを書いています。
https://swagger.io/specification/
ほぼ全てのエンドポイントに影響のあるリファクタリングをした時に、動作確認が辛すぎたのでインテグレーションテストを書くことにしました。
できたものは以下になります。
integration-test/integration_test at main · yutakahashi114/integration-test
Contribute to yutakahashi114/integration-test development by creating an
account
on GitHub.GitHubyutakahashi114
[https://github.com/yutakahashi114/integration-test/tree/main/integration_test]
以下、簡単に説明を書いていきます。
サンプルアプリケーション
以下のschemaを元にサーバーを実装します。ユーザーの一覧取得、ID指定の取得、登録、更※まだプレビュー版です(2021/09/18)
Cloud Run gets always-on CPU allocation | Google Cloud BlogNew always-on CPU
allocation for Cloud Run opens the doors to running workloads that use
background processes.Google Cloud BlogSteren Giannini Senior Product Manager
[https://cloud.google.com/blog/products/serverless/cloud-run-gets-always-on-cpu-allocation]
今まではRequestがきてからResponseを返すまでCPUの割当がされていました。しかしそれでは下記のSlackのような3秒以内レスポンスを返す必要があるケースにて困ることが多々有りました。基本的にこのようなAPIはレスポンスを先に返してしまい、あとで別途非同期で処理を実行するという形で対応していましたが、そgomockでスライスを順不同で一致させるためにMatcherを自作しました。
※追記
v1.6.0 から InAnyOrder というMatcherが用意されているので、不要になりました。
gomock package - github.com/golang/mock/gomock - pkg.go.devGo
[https://pkg.go.dev/github.com/golang/mock/gomock#InAnyOrder]gomock
GolangでUTを書く際に使います。
GitHub - golang/mock: GoMock is a mocking framework for the Go programming
language.GoMock is a mocking framework for the Go programming language. -
GitHub
- golang/mock: GoMock is a mocking framework for the Go programming language.
GitHubgolang [htt