LoginSignup
6
6

More than 5 years have passed since last update.

Goで簡易Webアプリ作成&Dockerコンテナにまとめる

Posted at

連休で少し時間ができたので新しいものに挑戦しようとGoに触り始めました。
折角なら馴染みのあるWebアプリを作成して、Dockerコンテナにまとめようっていう記事です。Goの文法やGOPATHのことは今回は載せてません。

この記事のソースコード見たいよって方は以下のリンクからどうぞ。
https://github.com/sasaken555/ponz_goecho_server

Go製のWebアプリを作る

さっそく本題。Webアプリとしては最低限文字列とJSON返すだけでよかったので、簡易なものとしてピッタリな Echo を選択。公式のドキュメントは分かりやすいし、サイトも見やすいので取っ付きやすいです。

[Echo]
https://echo.labstack.com/:embed

"minimalist Go web framework" と謳うくらいだから、Node.jsでいうExpress.jsと同じような位置付けなのかな?

※ 参考までに...
* Echo(Go): High performance, extensible, minimalist Go web framework
* Express.js(Node.js): Fast, unopinionated, minimalist web framework for Node.js

以下、公式の例を使いつつルーティングとミドルウェアを設定した main.go 例です。

ルーティングのミドルウェアはデフォルトの設定を使うとJSON形式の見辛いログが出力されるので、Apacheの Common Log Format に似せて出力されるように設定してます。ログ出力は出力内容は柔軟に変えられるみたいですね。

package main

import (
    "net/http"
    "github.com/labstack/echo"
    "github.com/labstack/echo/middleware"
    "github.com/sasaken555/ponz_goecho_server/routes"
)

func main() {
    /* Echoインスタンスの作成 */
    e := echo.New()

    /* Root Level Middleware */
    // ログ出力は Apache Common Log Format っぽく設定すると読みやすい
    e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
        Format: "${host} [${time_rfc3339_nano}] \"${method} ${uri}\" ${status} ${bytes_in} ${bytes_out}\n",
    }))
    e.Use(middleware.Recover())

    /* ルーティングの設定 */
    // 第2引数の関数は別パッケージに外出しすると分かりやすい
    e.GET("/users/:id", routes.GetUser)
    e.GET("/users/json", routes.GetJSONUser)

    e.Logger.Fatal(e.Start(":1323")) // ポート1323で起動。
}

また、ルーティングの設定・関数は main に全部突っ込むと後で見づらくなるので、外出ししてあげます。今回は routes/user.go としてルーティングしたときに返す関数をまとめる。

package routes

import (
    "net/http"
    "strconv"
    "github.com/labstack/echo"
    "github.com/sasaken555/ponz_goecho_server/util"
)

// GetUser ... Pathパラメータからユーザー(=ID)を取り出して返す
func GetUser(c echo.Context) error {
    // User ID from Path Parameter `users/:id`
    id := c.Param("id")
    return c.String(http.StatusOK, id)
}

// Customer ... 顧客情報の構造体
type Customer struct {
    ID        int64  `json:"id" xml:"id"`
    Name      string `json:"name" xml:"name"`
    OrderNum  int    `json:"ordernum" xml:"ordernum"`
    OrderProd string `json:"orderprod" xml:"orderprod"`
}

// GetJSONUser ... 顧客情報のJSONを返す
func GetJSONUser(c echo.Context) error {
    userID, err := strconv.ParseInt(c.QueryParam("userId"), 10, 0)  // strconvで文字列から整数に型変換
    userName := c.QueryParam("userName")
    orderNum := util.GetRand(100)  // 別のパッケージからインポートした指定桁数で乱数を返す関数を使う

    // userIDが整数でない(=型変換できない)ならば500エラーを返す
    if err != nil {
        return echo.NewHTTPError(http.StatusInternalServerError, "You Should Provide userId as Integer!")
    }

    // 構造体のポインタを作成
    u := &Customer{
        ID:        userID,
        Name:      userName,
        OrderNum:  orderNum,
        OrderProd: "Blend Coffee",
    }

    return c.JSON(http.StatusOK, u)
}

Dockerコンテナにまとめる

Dockerコンテナにソース含めて全部入れてビルドすると悩むのが、ビルドイメージ大きすぎ問題。

  • 実行ファイルのビルド時に、ランタイムとアプリで使うパッケージを全て実行ファイルの中に取り込むため、少しコード量の多いアプリでも成果物が数百MBになるのもザラ。

  • 実行ファイルを組み込んだDockerコンテナのイメージがデカイとなると、docker pull の度にディスクと時間が取られるのでポータビリティの点から使いづらい。

上記の問題があって頭を抱えましたが、ベースイメージを小さいものにするのに加えて、Dockerのマルチステージビルドで対応することしました。

※ 参考記事
Use multi-stage builds
Docker multi stage buildで変わるDockerfileの常識

通常だとソースコードを全てDockerコンテナに含めますが、Goは良くも悪くもソースコードから実行ファイルを作成するので、最終成果物の実行ファイルだけコンテナイメージに入っていればOKと考える。下のコード例の(1)が実行ファイル作成のビルド、(2)が(1)で作った実行ファイルをコピーして作った最終系のコンテナイメージになります。

ベースイメージも alpine イメージを使うことでさらに軽量化してます。

 
# Full SDK version ... (1)
FROM golang:1.10-alpine AS build

RUN apk update && apk upgrade \
    && apk add curl git

RUN curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh

WORKDIR /go/src/github.com/sasaken555/ponz_goecho_server
COPY . .

RUN dep ensure
RUN go build -o ponz_goecho_server

# Final Output ... (2)
FROM golang:1.10-alpine
COPY --from=build /go/src/github.com/sasaken555/ponz_goecho_server/ponz_goecho_server /bin/ponz_goecho_server
CMD /bin/ponz_goecho_server

結果として、通常のベースイメージ+アプリソースを含めてビルドしたイメージ(tag: heavy)とalpineベースイメージ+マルチステージビルドのイメージ(tag: light)で比較するとサイズは半分以下に抑えられていますね!やったね!

 
$ docker image ls ponz_goecho_server

REPOSITORY           TAG                 IMAGE ID            CREATED              SIZE
ponz_goecho_server   light               24b61e6c4f83        7 seconds ago        386MB
ponz_goecho_server   heavy               20bac15a0acf        About a minute ago   908MB

Goの謳い文句通りシンプルかつ効率的に開発&ビルドできたのはすごい...!!

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6