技術的な話

DockerコンテナでGolangからMongoDBに接続してみる

ちょっとしたWebアプリを開発をVue.js+Go+MongoDBで作成したいなーと思い立ち、色々覚えたこと調べたことをメモ書きや振り返っていきます。

なお、筆者はGoとMongoDBは完全初見。

作ったアプリはまた別の形で公開しようかなと考え中です。

環境

  • Windows 11
  • Virtual Box 6.1(ゲスト:Ubuntu 20.04)
  • Docker 20.10.11
  • Go 1.17.6
  • MongoDB 5.0.6

フォルダ構成

Goのフォルダ構成はとりあえず適当です。後々ファイルが多くなってきたら修正していきたい…。

あとは適当にDockerファイルなんかを配置したりしています。

$ tree
.
├── docker-compose.yml
├── go
│   ├── app
│   │   ├── go.mod
│   │   ├── go.sum
│   │   └── mongo.go
│   └── config
│       └── Dockerfile
└── mongo
    └── docker-entrypoint-initdb.d
        ├── 01_init.js
        ├── 02_init.sh
        └── sample.json

Dockerの設定周り

MongoDBはイメージそのまま使用しています。
Goのイメージは設定を少し弄りたかったのでDockerfileを使用しています。

MongoDBコンテナの設定周り

MongoDBはそのままイメージを使用するのですが、一部環境設定をenvironmentに記述しています。rootユーザのusername及びpasswordです。

他に設定可能な情報は公式を参照してください。

あとは/docker-entrypoint-initdb.d以下にスクリプトとデータを配置してコンテナ起動時に初期データを投入しています。

version: '3.9'

services:
  go:
    container_name: go
    build:
      context: ./go
      dockerfile: ./config/Dockerfile
    restart: unless-stopped
    tty: true
    volumes:
      - type: bind
        source: ./go/app
        target: /go/src/app

  mongo:
    container_name: mongo
    image: mongo:5.0.6-focal
    restart: unless-stopped
    environment:
      - MONGO_INITDB_ROOT_USERNAME=root
      - MONGO_INITDB_ROOT_PASSWORD=password
      - MONGO_INITDB_DATABASE=sample
      - TZ=Asia/Tokyo
    ports:
      - 27017:27017
    volumes:
      - type: bind
        source: ./mongo/docker-entrypoint-initdb.d
        target: /docker-entrypoint-initdb.d
      - type: volume
        source: mongo
        target: /data/db
        volume:
          nocopy: true
      - type: volume
        source: mongo
        target: /data/configdb
        volume:
          nocopy: true
volumes:
  mongo:

Goコンテナの設定周り

composeファイルの設定に加えて別途Dockerfileにも設定を記述しています。必要なソフトウェア類をインストールしているだけですが。
Goのコンテナイメージを扱うのは初なのでこの辺はもう少し良い方法を模索していきたいなと思っています。

FROM golang:1.17.6-bullseye

RUN apt-get update && \
    apt-get install -y \
        git \
        curl

# clean
RUN apt-get clean && \
    rm -rf /var/lib/apt/lists/*

RUN mkdir -p /go/src/app
COPY ./app /go/src/app

WORKDIR /go/src

MongoDBの初期設定、初期データスクリプト

以下のファイルで初期設定と初期データを投入しています。

  • 01_init.js
  • 02_init.sh
  • sample.json

01_init.jsではユーザ、データベースを作成するスクリプトです。
sampleデータベースの作成とそこにアクセス可能なユーザ(mongo)を作成しています。作成したデータベース上にはsample_collectionというcollectionも併せて作成しています。

var user = {
    user: "mongo",
    pwd: "mongo",
    roles: [
      {
        role: "dbOwner",
        db: "sample"
      }
    ]
  };
  
db.createUser(user);
db.createCollection("sample_collection");

02_init.sh及びsample.jsonでは作成したcollectionに対して初期データを投入しています。

mongoimport -u mongo -p mongo --db sample --collection sample_collection --file /docker-entrypoint-initdb.d/sample.json --jsonArray

初期データは特に意味の無いデータです。

[
    {
        "sampleDate": "2021-01-01 10:00:00",
        "username": "sample_user_001"
    },
    {
        "sampleDate": "2021-01-02 10:00:00",
        "username": "sample_user_002"
    },
    {
        "sampleDate": "2021-01-03 10:00:00",
        "username": "sample_user_003"
    }
]

Goコンテナからの接続

Goの接続ドライバーにはmongo-go-driverを使用しました。

MongoDBに接続して、初期データを一覧表示させるところまで実装していきます。
GoコンテナからはMongoDBのサービス名「mongo」で接続が可能です。ユーザ名やパスワードはURLに直接指定しています。
credential情報にユーザ名とパスワードをSetAuth経由で設定して接続しようとした場合、以下のエラーが発生し接続出来ませんでした。何故だろう…。

connection() error occured during connection handshake: auth error: sasl conversation error: unable to authenticate using mechanism "SCRAM-SHA-1": (AuthenticationFailed) Authentication failed.

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
	defer cancel()

	client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://mongo:mongo@mongo:27017/sample"))
	if err != nil {
		log.Fatal(err)
	}

	log.Println("fetch start.")

	collection := client.Database("sample").Collection("sample_collection")
	cursor, err := collection.Find(context.Background(), bson.D{})
	if err != nil {
		log.Fatal(err)
	}

	for cursor.Next(context.Background()) {
		result := struct {
			ID              primitive.ObjectID
			sampleDate      string
			username        string
		}{}

		err := cursor.Decode(&result)

		if err != nil {
			log.Fatal(err)
		}
		raw := cursor.Current
		log.Println(raw)
	}
	log.Println(collection)
	// Close connection
	client.Disconnect(ctx)
}

実行結果は以下になります。

root@24c511c33904:/go/src/app# go run main.go 
2022/02/15 14:03:57 fetch start.
2022/02/15 14:03:57 {"_id": {"$oid":"620a67eaf3e939096fedc06b"},"sampleDate": "2021-01-01 10:00:00","username": "sample_user_001"}
2022/02/15 14:03:57 {"_id": {"$oid":"620a67eaf3e939096fedc06c"},"sampleDate": "2021-01-02 10:00:00","username": "sample_user_002"}
2022/02/15 14:03:57 {"_id": {"$oid":"620a67eaf3e939096fedc06d"},"sampleDate": "2021-01-03 10:00:00","username": "sample_user_003"}
2022/02/15 14:03:57 &{0xc00015a4e0 0xc0000a8600 weight 0xc0000919a0 <nil> 0xd5bf40 0xc0000a2270 0xc0000a2288 0xc0000b8bd0}

とりあえず接続するところまでは出来ました。

もう少しソースや構成を変更してみようかと思います。

-技術的な話
-, , ,