Goコンテナでフロントとバックエンドを分けてみる

今日はGo言語で簡単なwebアプリケーションを作成します。webとdbにコンテナを分けて、webページにdbから取得したデータを表示したいと思います。以下のように、マイクロサービスを意識して、コンテナ間通信をhttpリクエストで行いたいと思います。

以下の流れで作成します。

  • dbを作成する
  • webを作成する
    • ソースコードを作成する
      • main.goを作成する
      • layout.htmlを作成する
      • index.htmlを作成する
    • Dockerfileを作成する
  • コンテナを実行する
    • コンテナイメージの作成
    • Docker Composeファイルを作成する
    • コンテナを実行する

dbを作成する

 dbコンテナは以下の記事で作成したものを利用します。ここで作成したのは、httpリクエストを行うとjsonファイルを読み込んで出力するコンテナです。本来であればデータベースも作成しますが、今回はhttpリクエストでやり取りするのをメインにしているので、データはファイルにします。データベースを使うのはまた記事書きたいと思います。

go言語でjsonファイルを読み込んで表示してみる

webを作成する

ソースコードを作成する

main.goを作成する

 ポート8080で受け付けると、dbコンテナから取得したjsonコードをhtml形式で表示するwebのソースコード(main.go)を以下に示します。

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "github.com/gorilla/mux"
    "io/ioutil"
    "os"
        "html/template"
)

type category struct {
    Categories []struct{
      Name string `json:"name"`
    }`json:"categories"`
}

func main() {
    r := mux.NewRouter()
    r.Handle("/", index)

    //サーバー起動
    if err := http.ListenAndServe(":8080", r); err != nil {
        log.Fatal("ListenAndServe:", nil)
    }
}

var index = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    url := "http://db:8082/public"
    req, _ := http.NewRequest("GET", url, nil)
    client := new(http.Client)
    resp, _ := client.Do(req)
    defer resp.Body.Close()
    byteArray, _ := ioutil.ReadAll(resp.Body)
    var categories category
    if err := json.Unmarshal(byteArray, &categories); err != nil {
            log.Fatal(err)
            os.Exit(1)
    }
    generateHTML(w, categories, "layout", "index")
})

func generateHTML(writer http.ResponseWriter, data interface{}, filenames ...string) {
    var files []string
    for _, file := range filenames {
        files = append(files, fmt.Sprintf("templates/%s.html", file))
    }

    templates := template.Must(template.ParseFiles(files...))
    templates.ExecuteTemplate(writer, "layout", data)
}

 index = http.HandlerFuncでは、dbコンテナから取得したjsonコードを構造体categoryの変数categoriesに代入し、generateHTML関数を呼び出します。generateHTMLは引数で与えられた名前のhtmlファイル、ここではlayout.htmlとindex.htmlを使ってページを生成します。

layout.htmlを作成する

 generateHTML関数は、layout.htmlとindex.htmlを使ってページを生成します。layout.htmlは以下の通り、body以外のheaderのみを記述しています。layoutを作成することで、それぞれのhtmlのheaderを統一し、ソースコードの重複を回避できます。

{{ define "layout" }}

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Test</title>
  </head>
  <body>
    {{ template "content" . }}
  </body>
</html>

{{ end }}

index.htmlを作成する

 このlayoutを使ったindex.htmlが以下になります。{{ define "content" }} と{{ end }}で囲んだhtml文がlayoutの{{ template "content" . }}、つまりはページの本文に記述されることになります。

{{ define "content" }}
  <center>
    {{ range .Categories }}
        {{ .Name }}<br>
    {{ end }}
  </center>
{{ end }}

Dockerfileを作成する

 コンテナイメージを作成するのに必要なDockerfileを作成します。ビルド用のディレクトリ構造は以下の通りです。

$ tree web
web
├── Dockerfile
└── src
    ├── main.go
    └── templates
        ├── index.html
        └── layout.html

 src配下に作成したソースコードを配置します。このディレクトリ構造に合わせて作成したDockerfileが以下になります。

FROM golang:latest

# コンテナ作業ディレクトリの変更
WORKDIR /go/src/web
# ホストOSの ./src の中身を作業ディレクトリにコピー
COPY ./src .
RUN go get -u github.com/gorilla/mux
RUN go build -o web
# ウェブアプリケーション実行コマンドの実行
CMD ["./web"]

コンテナを実行する

コンテナイメージを作成する

 上記で作成したwebとdbのコンテナイメージを以下のコマンドで作成します。

$ docker build -t web:latest web/
$ docker build -t db:latest db/

Docker Composeファイルを作成する

 webとdbのコンテナイメージからコンテナを実行しますが、webとdb間は通信できる必要があります。そこでdocker-compose.yamlファイルを作成します。以下にその内容を示します。

version: '3'
services:
  db:
    container_name: db
    image: db:latest
    ports:
      - "8082:8082"
  web:
    container_name: web
    image: web:latest
    depends_on:
      - db
    links:
      - db
    ports:
      - "8080:8080"

 docker-compose.yamlはservicesブロックの中に実行するコンテナ毎にブロックを作成します。今回はwebとdbコンテナだけなので、webブロックとdbブロックを作成しました。
 webのmain.goにurl := "http://db:8082/public"とurlを直接指定しました。従ってwebコンテナはホスト名dbをdbコンテナに名前解決できる必要があります。それを定義しているのがlinksです。

コンテナを実行する

 docker-compose.yamlファイルからコンテナを実行するにはdocker-composeコマンドを利用します。docker-compose.yamlを置いたディレクトリで以下のコマンドを実行します。

$ docker-compose up -d
Creating db ... done
Creating web ... done
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS              PORTS                    NAMES
da224d318f33        web:latest          "./web"             About a minute ago   Up About a minute   0.0.0.0:8080->8080/tcp   web
b245c4eb8320        db:latest           "./searcher"        About a minute ago   Up About a minute   0.0.0.0:8082->8082/tcp   db

 docker-compose.yamlで定義した内容で実行されていることがわかります。ブラウザでhttp://localhost:8080/にアクセスしてみましょう。jsonの中身が表示されていることがわかります。

以上です!なんとなくGo言語がわかってきたようなきてないような。。ちゃんと仕組みから理解した方が良さそうなので、またそういう記事も書きたいと思います!ソースコードが汚いのは許してください。。。