sts-250rrです。
今回はラクス Advent Calendar 2018に投稿した記事、「開発環境を作るためにDockerを使った話」の続きになります。
はじめに
前回の記事では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コンテナの起動
コンテナを起動して、開発環境コンテナの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でこんな違いがありました。
深くまで追えていませんがイメージ作成時に実行しておかないとmain.goの実行時にはDLが完了できないとかなんでしょうか?
PostgreSQLコンテナで初期DB構築を行う
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がよしなにやってくれる分、上手くいかなかった時にはどこに問題があるのかを見つけるのが大変です。 その分出来上がってしまえば作り直しや複製が簡単にできてしまうので、便利であることに変わりはないですね。
個人的には、「実は・・・」でお伝えした部分はなんとか解決したいものです。。。
エンジニア中途採用サイト
ラクスでは、エンジニア・デザイナーの中途採用を積極的に行っております!
ご興味ありましたら是非ご確認をお願いします。
https://career-recruit.rakus.co.jp/career_engineer/カジュアル面談お申込みフォーム
どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。
以下フォームよりお申込みください。
rakus.hubspotpagebuilder.comラクスDevelopers登録フォーム
https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/イベント情報
会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください!
◆TECH PLAY
techplay.jp
◆connpass
rakus.connpass.com