20200104のGoに関する記事は5件です。

Docker コンテナ内で立ち上げた godoc にホスト側からアクセスする

この記事について

Go言語の勉強用の環境として立ち上げた Docker コンテナ内で godoc サーバを立ち上げ、ホストOS側のブラウザからアクセスしてみたところ、アクセスできなかったので調べてみたメモ。

用意した Dockerfile

Dockerfile
FROM golang:latest

RUN mkdir /go/src/app
WORKDIR /go/src/app
RUN go get golang.org/x/tools/cmd/godoc
EXPOSE 6060/tcp

実行したコマンド1(godocがインストールされたコンテナのビルドと、そのコンテナでのgodocサーバの起動)

$ docker build -t hoge .
$ docker run --rm -p 6060:6060 hoge godoc

実行したコマンド2(ホストからgodocサーバにアクセス)

$ curl http://localhost:6060
curl: (52) Empty reply from server

上記のようにサーバから正常な応答が返ってこない。

上記のURLにアクセスした場合の期待する応答は、以下のようなリダイレクトのレスポンス。

$ curl -v http://localhost:6060
* Rebuilt URL to: http://localhost:6060/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 6060 (#0)
> GET / HTTP/1.1
> Host: localhost:6060
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 302 Found
< Content-Type: text/html; charset=utf-8
< Location: /pkg/
< Date: Sat, 04 Jan 2020 12:58:12 GMT
< Content-Length: 28
<
<a href="/pkg/">Found</a>.

* Connection #0 to host localhost left intact

対処

godoc サーバを起動するコマンドを以下のように変更

$ docker run --rm -p 127.0.0.1:6060:6060 hoge godoc -http 0.0.0.0:6060
  • godoc の起動オプション -http 0.0.0.0:6060 を付与し、コンテナのすべてのネットワークインターフェースからの接続を受け付けるようにする。
  • docker run でコンテナ起動時に -p 172.0.0.1:6060:6060 のようにホスト側のネットワークインターフェースをローカル・ループバック・アドレスに限定する。
    • 別に -p 6060:6060 でもホストからアクセス可能だが、ホストと同じネットワークに存在する別の端末からもアクセス可能になってしまう。(参考
    • それでも構わない、あるいは、むしろ別端末アクセスさせたいというケースでは 172.0.0.1: の部分は省略しても構わない。

上記のようにサーバを起動したあと、ホスト側のブラウザから http://localhost:6060 にアクセスすると、無事 GoDoc のページが表示できた。

Image from Gyazo

参考

以下の記事がより詳しい

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

Docker コンテナ内で立ち上げた godoc にホスト側からアクセスできなかった

この記事について

Go言語の勉強用の環境として立ち上げた Docker コンテナ内で godoc サーバを立ち上げたが、ホストOS側のブラウザからアクセスできなかったので調べてみたメモ。

環境

用意した Dockerfile

Dockerfile
FROM golang:latest

RUN mkdir /go/src/app
WORKDIR /go/src/app
RUN go get golang.org/x/tools/cmd/godoc
EXPOSE 6060/tcp

実行したコマンド1(godocがインストールされたコンテナのビルドと、そのコンテナでのgodocサーバの起動)

$ docker build -t hoge .
$ docker run --rm -p 6060:6060 hoge godoc

実行したコマンド2(ホストからgodocサーバにアクセス)

$ curl http://localhost:6060
curl: (52) Empty reply from server

上記のようにサーバから正常な応答が返ってこない。

上記のURLにアクセスした場合の期待する応答は、以下のようなリダイレクトのレスポンス。

$ curl -v http://localhost:6060
* Rebuilt URL to: http://localhost:6060/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 6060 (#0)
> GET / HTTP/1.1
> Host: localhost:6060
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 302 Found
< Content-Type: text/html; charset=utf-8
< Location: /pkg/
< Date: Sat, 04 Jan 2020 12:58:12 GMT
< Content-Length: 28
<
<a href="/pkg/">Found</a>.

* Connection #0 to host localhost left intact

対処

godoc サーバを起動するコマンドを以下のように変更

$ docker run --rm -p 127.0.0.1:6060:6060 hoge godoc -http 0.0.0.0:6060
  • godoc の起動オプション -http 0.0.0.0:6060 を付与し、コンテナのすべてのネットワークインターフェースからの接続を受け付けるようにする。
  • docker run でコンテナ起動時に -p 172.0.0.1:6060:6060 のようにホスト側のネットワークインターフェースをローカル・ループバック・アドレスに限定する。
    • 別に -p 6060:6060 でもホストからアクセス可能だが、ホストと同じネットワークに存在する別の端末からもアクセス可能になってしまう。(参考
    • それでも構わない、あるいは、むしろ別端末アクセスさせたいというケースでは 172.0.0.1: の部分は省略しても構わない。

上記のようにサーバを起動したあと、ホスト側のブラウザから http://localhost:6060 にアクセスすると、無事 GoDoc のページが表示できた。

Image from Gyazo

参考

以下の記事がより詳しい

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

Go1.13とDockerで開発環境構築(ホットリロード有)

はじめに(背景)

筆者は今までGoでは簡単なツール(Slack用Botとか)しか作ってこなかったが、
Goを使ってWEBアプリを開発してみることにした。 

ところが、開発環境を構築するのに、検索でヒットする情報が微妙だった
- Goのバージョンが古くてGo Modulesを使っていなかったり、
- 使っているライブラリの最終更新が3年前だったり

情報を整理しながら開発環境を構築する必要が有ったので、
せっかくだからアウトプットしておこうと思った。

前提

  • Goのversionは、執筆時点で最新の1.13系を使う
  • docker-composeで立ち上げたい
  • せっかくなので、ホットリロードが欲しい

以下には触れません

  • GoでのWEBサーバーの立ち上げ方
  • Go Modulesについて
  • Docker、docker-composeの使い方

本題

Goの設定

Go Modulesを使う

Go Modulesの導入や使い方などは割愛
↓のコマンドでinitialize
go mod init github.com/username/project

ホットリロードの設定

色々調べたら、Realizeが良さそう(適当)
https://github.com/oxequa/realize

設定ファイル(後述)を用意して、
realize startコマンドを実行するだけ

Docker環境用意

Dockerfile

FROM golang:1.13-alpine

WORKDIR /app

COPY . .

RUN apk add --no-cache git \
  && go get gopkg.in/urfave/cli.v2@master \
  && go get github.com/oxequa/realize

 
.realize.yml (ホットリロードの設定)

settings:
  legacy:
    force: false
    interval: 0s
schema:
- name: hoge
  path: path/to/server
  commands:
    install:
      status: true
    run:
      status: true
  watcher:
    extensions:
    - go
    paths:
    - path/from/server
    ignore_paths:
    - .git

 
docker-compose.yml

version: "3"
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - 8080:8080
    volumes:
      - ./:/app
    command: realize start

 

※realizeの設定でエラーが出た。(↑のファイルには反映済み)

パッケージを取得する時
解決方法は以下のissueから
https://github.com/oxequa/realize/issues/253
↓の箇所

RUN apk add --no-cache git \
  && go get gopkg.in/urfave/cli.v2@master \
  && go get github.com/oxequa/realize

 

Go Modules環境起因でもエラー
以下のissueから
https://github.com/oxequa/realize/issues/217
↓の箇所

  commands:
    install:
      status: true

おわりに

以上で、DockerでGoを開発する環境ができました。
あとはdocker-composeにDBなり何なりを追加すれば進められそうです。

 

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

database/sql で INSERT したレコードのIDを取得する

概要

Go の database/sql を利用して、 INSERT したレコードのIDを取得する

参考

問題

  • 使用しようとした lib/pq では LastInsertId() (int64, error) が使えない
  • database/sqlint64 で ID を返す Interface しか存在しない

使用しようとした lib/pq では LastInsertId() (int64, error) が使えない

database/sql では import する Driver を差し替えることで、異なる SQL Database を利用する場合でも、共通の Interface を利用して SQL を扱える。

今回利用しようとしたのは、 PostgreSQL 用の Driver の一つである lib/pq

しかしながら、 Result Interface のコメントを読むと下記が書かれており、全てのデータベースでのサポートではないことがわかる。また実際に lib/pq でこのメソッドを呼び出そうとしてもエラーが返り、 lib/pq では INSERT したレコードの ID を取得できなかった。

// LastInsertId returns the integer generated by the database
// in response to a command. Typically this will be from an
// "auto increment" column when inserting a new row. Not all
// databases support this feature, and the syntax of such
// statements varies.

database/sqlint64 で ID を返す Interface しか存在しない

また LastInsertId() は auto increment された int64 型の ID を取得する Interface であり、 ID を UUID 型に指定した場合などは、利用できないことがわかる。

解決策

Instead, we get around this by making a few changes. First, we are going to update our SQL statement by adding RETURNING id to the end of the statement. This will tell our SQL database that we want it to return the id of the newly inserted record so that we can do something with it.

その為、まず SQL 側の解決策として、 Result Interface では LastInsertID を利用するのではなく、 SQLを変更して、 Insert した後の レコードの ID を返却するように変更する。

2つ目に Go のコード側も変更する。
database/sql では UPDATE・INSERT は基本的に ~.Exec を利用して、 Result, error を取得し、 SELECT の場合は、 ~.Query を利用する。

下記は元のコード。

    stmt, err := prepare(repo, "INSERT INTO users(username, password, email) VALUES($1, $2, $3);")
    defer stmt.Close()
    if err != nil {
        return nil, fmt.Errorf("query prepare faild: %w", err)
    }

    _, err = stmt.Exec(user.Personal.Name, user.Password, user.Personal.Email)
    if err != nil {
        return nil, fmt.Errorf("exec faild: %w", err)
    }

これを下記に変更する。
QueryRow を使用することで、 Row が返り値として戻ってくるようになり、 ~ RETURNING user_id; の部分を実行結果として、受け取れるようになる。あとは Row.Scan() して、レコードの値を読み取ることで、 INSERT したレコードの ID を受け取ることができる。

    stmt, err := prepare(repo, "INSERT INTO users(username, password, email) VALUES($1, $2, $3) RETURNING user_id;")
    defer stmt.Close()
    if err != nil {
        return nil, fmt.Errorf("query prepare faild: %w", err)
    }

    err = stmt.QueryRow(user.Personal.Name, user.Password, user.Personal.Email).Scan(&user.Id)
    if err != nil {
        return nil, fmt.Errorf("query faild: %w", err)
    }

これで、 LastInsertId() 介さないので、 Driver が対応していなくてもよく、また ID の形式も int でなくてもよくなった。

以上、慣れないのでどうするのかわからなかった内容を備忘録として。
誰かの参考になれば幸いです。

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

Echo+GORMでRESTfulなAPIサーバをつくる

Overview

小規模なプロダクトにおいてPythonでAPIサーバはしんどい(できるけどソースが美しくない)ので, 全然使ったことないけどGoでAPIサーバを立ててみたい

Description

普段お仕事ではDjango REST Framework, 趣味ではFlaskを使っているんですが, PythonでORマッパー使おうとするとSQLAlchemyとか使うハメになってしんどいです.

かといってDjangoを使うのはウェイトが重い...
peeweeとかいういい感じのライブラリもあるっぽいですが

ということでせっかくなのでGoで作ってみます

今回は, 要素を一意に特定できるidと文字列textを要素に持つPostを定義して, API経由でCRUD操作を行うようなアプリを作ります

GET: localhost:1323/posts -> 全postの取得
GET: localhost:1323/posts/:id -> id指定でpost取得
PUT: localhost:1323/posts/:id -> id指定でpost更新
POST: localhost:1323/posts/:id -> id指定でpost作成
DELETE: localhost:1323/posts/:id -> id指定でpost削除

Settings

DockerでGoの開発環境を整えます

とりあえず動かしたいという方は後述のソースをまるっとコピりましょう

環境構築について, こちらの記事がわかりやすいです.
Golang(Echo) x docker-composeでホットリロード用いた開発

ディレクトリ構造

.
├── Dockerfile
├── docker-compose.yml
└── src
    ├── main.go
    ├── go.mod
    ├── go.sum
    ├── controller
    │   └── sqlite.go
    └── view
        └── post.go

プログラム本体

ディレクトリ構造に合わせてコピる

main.go

GET, POST, PUT, DELETEなど各メソッドを使える

package main

import (
    "github.com/labstack/echo"
    view "app/view"
)

func main() {
    e := echo.New()
    initRouting(e)
    e.Logger.Fatal(e.Start(":1323"))
}

func initRouting(e *echo.Echo) {
    e.GET("/posts", view.GetAllPosts)
    e.GET("/posts/:id", view.GetPost)
    e.POST("/posts", view.CreatePost)
    e.PUT("/posts/:id", view.UpdatePost)
    e.DELETE("/posts/:id", view.DeletePost)
}

post.go

json:idでレスポンスのjsonのキー名を指定できる. デフォだとPascalCaseになっちゃうのがウザいので設定

gorm:hogeで各属性を指定できる

これDB接続のところDecoratorみたいな感じで綺麗に共通化できないんでしょうか...
詳しい方教えて下さい

package view

import (
    "net/http"
    "github.com/labstack/echo"
    . "app/controller"
)

type Post struct {
    Id int `json:"id" gorm:"primary_key;AUTO_INCREMENT"`
    Text string `json:"text"`
}

func GetAllPosts(c echo.Context) error {
    db := OpenSQLiteConnection()
    defer db.Close()
    db.AutoMigrate(&Post{})

    var posts []Post
    db.Find(&posts)
    return c.JSON(http.StatusOK, posts)
}

func GetPost(c echo.Context) error {
    db := OpenSQLiteConnection()
    defer db.Close()
    db.AutoMigrate(&Post{})

    if id := c.Param("id"); id != "" {
        var post Post
        db.First(&post, id)
        return c.JSON(http.StatusOK, post)
    } else {
        return c.JSON(http.StatusNotFound, nil)
    }
}

func CreatePost(c echo.Context) error {
    db := OpenSQLiteConnection()
    defer db.Close()
    db.AutoMigrate(&Post{})

    post := new(Post)
    if err := c.Bind(post); err != nil {
        return err
    }
    db.Create(&post)

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

func UpdatePost(c echo.Context) error {
    db := OpenSQLiteConnection()
    defer db.Close()

    newPost := new(Post)
    if err := c.Bind(newPost); err != nil {
        return err
    }

    if id := c.Param("id"); id != "" {
        var post Post
        db.First(&post, id).Update(newPost)
        return c.JSON(http.StatusOK, post)
    } else {
        return c.JSON(http.StatusNotFound, nil)
    }

}

func DeletePost(c echo.Context) error {
    db := OpenSQLiteConnection()
    defer db.Close()

    if id := c.Param("id"); id != "" {
        var post Post
        db.First(&post, id)
        db.Delete(post)
        return c.JSON(http.StatusOK, post)
    } else {
        return c.JSON(http.StatusNotFound, nil)
    }
}

sqlite.go

hoge.sqlite3はDBを保存したいパスを指定
:memory: の方を使うとインメモリで使える

package controller

import (
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/sqlite"
)

func OpenSQLiteConnection() *gorm.DB {
    //db, err := gorm.Open("sqlite3", ":memory:")
    db, err := gorm.Open("sqlite3", "hoge.sqlite3")
    if err != nil {
        panic("failed to connect database.")
    }

    db.LogMode(true)
    return db
}

Docker関連ファイル

libc-dev, gccがないとrealize(ホットリロード用ライブラリ)がインストールできないのでインスコ

Dockerfile

FROM golang:1.13.5-alpine as build

WORKDIR /go/app

COPY src .

RUN apk add --no-cache git gcc libc-dev \
 && go build -o app \
 && go get gopkg.in/urfave/cli.v2@master \
 && go get github.com/oxequa/realize

FROM alpine

WORKDIR /app

COPY --from=build /go/app/app .

RUN addgroup go \
  && adduser -D -G go go \
  && chown -R go:go /app/app

CMD ["./app"]

docker-compose.yml

version: '3.5'

services:
  app:
    build:
      context: .
      target: build
    volumes:
      - ./src:/go/app
    command: realize start --run --no-config
    ports:
      - 1323:1323

Run

モジュール管理用ファイルのgo.modを生成

$ docker run --rm -v `pwd`:/go/app -w /go/app golang:1.13.5-alpine go mod init app

各ファイルを作り終わったら次のコマンドでイメージのビルド&実行

$ docker-compose build
$ docker-compose up

~
app_1  | [02:18:27][APP] : Install started
app_1  | [02:18:28][APP] : Install completed in 0.446 s
app_1  | [02:18:28][APP] : Running..
app_1  | [02:18:28][APP] :    ____    __
app_1  | [02:18:28][APP] :   / __/___/ /  ___
app_1  | [02:18:28][APP] :  / _// __/ _ \/ _ \
app_1  | [02:18:28][APP] : /___/\__/_//_/\___/ v3.3.10-dev
app_1  | [02:18:28][APP] : High performance, minimalist Go web framework
app_1  | [02:18:28][APP] : https://echo.labstack.com
app_1  | [02:18:28][APP] : ____________________________________O/_______
app_1  | [02:18:28][APP] :                                     O\
app_1  | [02:18:28][APP] : ⇨ http server started on [::]:1323

コンテナができたらプログラムが実行されます.
ソースコードを変更すると自動で更新されるのがいい感じです!

各メソッドが使えるかも試しておきましょう
curlでもいいですが, 直感的に使えるhttpieが便利です

# 全件取得(まだ0件)
$ http GET localhost:1323/posts

# 新規作成(idは自動インクリメント)
$ http POST localhost:1323/posts text=hoge
$ http GET localhost:1323/posts

# id=1のtextを更新
$ http PUT localhost:1323/posts/1 text=fuga
$ http GET localhost:1323/posts/1

# id=1を削除
$ http DELETE localhost:1323/posts/1
$ http GET localhost:1323/posts

Tips

go getなどコマンドを打ちたいときは

$ docker ps

で実行中のコンテナIDを調べて,

$ docker exec -it {CONTAINER_ID} sh

でコンテナ内に入れます

References

https://github.com/labstack/echo
https://github.com/jinzhu/gorm
Golang(Echo) x docker-composeでホットリロード用いた開発

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