sts-250rrです。
今回はラクス Advent Calendar 2018に投稿した記事、「開発環境を作るためにDockerを使った話」の続きになります。
qiita.com
はじめに
前回の記事ではDockerで開発環境を作ってみました。
が、このままではあまりにもチープな構成に感じます。。。
今回はコンテナ間通信を使って別コンテナに作成したDBのデータを取得できるような構成を作っていくことを目標とします。
目標とする形は以下のようなイメージです。
Dockerネットワークを作成してコンテナ間通信
コンテナ間通信を実現するために、Dockerネットワークを作成します。
Dockerネットワークはコンテナ名を指定することでアプリ用(開発環境用)のコンテナからDBコンテナに接続することができるようになります。
まずはDockerネットワークを作っていきます。
$ docker network create my-network
cbe89848f313a1b7766780903f79da2ff3a83bbe962f930c1f3caf9136fbac6f
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
39d283374d44 bridge bridge local
a3d0faef3da4 host host local
cbe89848f313 my-network bridge local
7b1a7347c6b9 none null local
以下のコマンドでDockerネットワークの詳細を確認することができます。
$ docker network inspect my-network
[
{
"Name": "my-network",
"Id": "cbe89848f313a1b7766780903f79da2ff3a83bbe962f930c1f3caf9136fbac6f",
"Created": "2018-12-09T02:41:41.8124636Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.21.0.0/16",
"Gateway": "172.21.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
作成したてのDockerネットワークなので何も登録されておらず、Containers部分が空っぽです。
コンテナ起動時にこのDockerネットワークを指定することで、同一のネットワークにコンテナが作成されるため、コンテナ間通信ができるようになります。
※細かい部分は詰められていません。
コンテナを起動して、開発環境コンテナのGoプログラムからPostgreSQLコンテナのDBに接続出来るような環境構築を行なっていきます。
今回もdocker-composeを利用していくのでディレクトリ構成・ファイルを以下のように作成します。
<任意のディレクトリ>
|-- init
| |-- 1_createdb.sql
|-- Dockerfile
|-- docker-compose.yml
|-- main.go
今回、main.go
からPostgreSQLコンテナのDBに接続するために各設定ファイルやDBの初期構築が必要になります。
各ファイルは以下のように作成します。
docker-compose.yml
version: '3'
services:
postgres:
image: postgres
environment:
POSTGRES_USER: postgres # user
POSTGRES_PASSWORD: postgres # pass
ports:
- "5432:5432"
volumes:
- ./db:/docker-entrypoint-initdb.d
networks:
- my-network
app:
build: .
ports:
- "8080:8080"
networks:
- my-network
volumes:
test_db:
external: false
networks:
my-network:
external: true
Dockerfile
#ベースのDockerイメージをgolangで指定
FROM golang:latest
EXPOSE 5000
#ワークディレクトリを設定する
WORKDIR /go
#ホストのディレクトリを/go配下にコピー
ADD . /go
#GOPATHの設定
ENV GOPATH $GOPATH:$HOME/go
RUN go get github.com/jinzhu/gorm
RUN go get github.com/lib/pq
#main.goを実行
CMD ["go", "run", "main.go"]
1_createdb.sql
create database testdb;
main.go
main.goはこちらを参考にさせていただきました。
実行するとDBに登録されているIDと名前を出力します。
https://qiita.com/rky_1011/items/9772d92b5fe8cb3b82b0qiita.com
package main
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/lib/pq"
)
type User struct {
ID int64 `gorm:"primary_key" json:"id"`
Name string `json:"name"`
}
type Users []User
func main() {
db, err := gorm.Open("postgres", "host=172.24.0.3 user=postgres port=5432 password=postgres dbname=testdb sslmode=disable")
if err != nil {
panic(err)
}
defer db.Close()
db.AutoMigrate(User{})
var user = User{Name: "testname"}
db.NewRecord(user)
db.Create(&user)
db.Save(&user)
var users = Users{}
db.Find(&users)
fmt.Println(users)
}
さて、ファイルが揃ったら、docker-compose build
、docker-compose up
を実行すれば、main.go
の実行結果が得られます。
(略)
postgres_1 | 2018-12-09 06:32:23.787 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
postgres_1 | 2018-12-09 06:32:23.829 UTC [58] LOG: database system was shut down at 2018-12-09 06:32:23 UTC
postgres_1 | 2018-12-09 06:32:23.851 UTC [1] LOG: database system is ready to accept connections
app_1 | [{1 testname}]
app_app_1 exited with code 0
postgres_1 |
やapp_1 |
といった形でコンテナごとの出力結果が得られています。app_1 | [{1 testname}]
と出力されているのでなんだかいけてそうな気がします。
これだけではピンとこないのでPostgreSQLコンテナに入ってDBを確認してみると、
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f9a9ed9b3fc8 postgres "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 0.0.0.0:5432->5432/tcp app_postgres_1
$ docker exec -it app_postgres_1 bash
root@f9a9ed9b3fc8:/# psql -U postgres testdb
psql (11.1 (Debian 11.1-1.pgdg90+1))
Type "help" for help.
testdb=# select * from users;
id | name
----+----------
1 | testname
(1 rows)
PostgreSQLコンテナの内容と開発環境コンテナのmain.go実行時の内容が一致するので正しく値を取得できているようです。
最後にDockerネットワークの状態を確認します。
docker network inspect my-network
[
{
"Name": "my-network",
"Id": "e77a834d8aff49e316e7ab153666232eab35f7fe4350b18be19da1d608125c4b",
"Created": "2018-12-09T06:25:02.1986525Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.24.0.0/16",
"Gateway": "172.24.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"f9a9ed9b3fc8b67232edb76ec4202e0b66ec776dbd0665db7f9ee11341088571": {
"Name": "app_postgres_1",
"EndpointID": "6dfd276d55c4352668364de97a588ca5072bf5ee647b8b9dbe5cbdc04d8603f1",
"MacAddress": "02:42:ac:18:00:03",
"IPv4Address": "172.24.0.3/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
Go側のコンテナは実行完了と同時に停止してしまうので、Containers
には表示されていませんが、Postgresコンテナがmy-network
内に含まれていることがわかります。
色々複雑でしたが、コンテナ間通信ができる開発環境、無事完成です。
今回のポイント
Goのパッケージインポート
ローカルでGoを書く時にも必要なことですが、軽く詰まりました。
main.goの中でGithubからパッケージをインポートしていますが、事前にGOPATHを設定したり、go getでGitリモートリポジトリをダウンロードしておかなければならなかったようです。
Dockerコンテナでもこれは同様なのでDockerfile上でENVやRUNで必要な事前処理を行っていますが、詰まったのはRUNでgo getを実行する部分です。
はじめはCMDで実行していましたが、main.go実行時にこうなります。
app_1 | main.go:6:4: cannot find package "github.com/jinzhu/gorm" in any of:
app_1 | /usr/local/go/src/github.com/jinzhu/gorm (from $GOROOT)
app_1 | /go/src/github.com/jinzhu/gorm (from $GOPATH)
app_1 | /go/src/src/github.com/jinzhu/gorm
app_1 | main.go:7:4: cannot find package "github.com/lib/pq" in any of:
app_1 | /usr/local/go/src/github.com/lib/pq (from $GOROOT)
app_1 | /go/src/github.com/lib/pq (from $GOPATH)
app_1 | /go/src/src/github.com/lib/pq
app_app_1 exited with code 1
パッケージ見つからないよ。という旨のエラーですね。
調べてみたらRUNとCMDでこんな違いがありました。
qiita.com
深くまで追えていませんがイメージ作成時に実行しておかないとmain.goの実行時にはDLが完了できないとかなんでしょうか?
PostgreSQLコンテナをただ作るのみではもちろんDBの作成はしていないので、接続に失敗します。かといって毎回コンテナを起動するたびに手動で作成するのはDockerの利点を潰してしまっていますよね。
そんなことをしなくて良いようにDockerhubの公式のイメージには便利機能がありました。
If you would like to do additional initialization in an image derived from this one, add one or more .sql, .sql.gz, or *.sh scripts under /docker-entrypoint-initdb.d (creating the directory if necessary).
/docker-entrypoint-initdb.d
というディレクトリに.sql
や.sh
のファイルを配置しておけば起動時に実行してくれるみたいです。
ということで、docker-compose.yml
のvolumes:
でinit
以下の1_createdb.sql
をdocker-entrypoint-initdb.d
に配置し、createdb
を実行していました。
実は・・・
必要なファイルが揃えば、さも簡単に実行できるかのように書いていますが、1度目の実行はほぼ必ず失敗します。
というのも1度目の実行ではDockerネットワーク上のPostgreSQLコンテナに割り振られるIPがわからないためです。。。
main.goの中で接続先のDBのあるIPを指定しているので、その部分でコケます。
多分何かしらいい方法があるんだと思いますが、現時点では見つからず。。。
良い方法があれば教えていただきたいです。。。
まとめ
今回はDockerネットワークを使ってGoアプリとDBを接続してみました。
アプリのコンテナとDBを分けることができ、なんだかそれらしくなってきたような気がします。
Dockerやdocker-composeがよしなにやってくれる分、上手くいかなかった時にはどこに問題があるのかを見つけるのが大変です。
その分出来上がってしまえば作り直しや複製が簡単にできてしまうので、便利であることに変わりはないですね。
個人的には、「実は・・・」でお伝えした部分はなんとか解決したいものです。。。
◆TECH PLAY
techplay.jp
◆connpass
rakus.connpass.com