[Windows] Dockerを使用してMosquittoでMQTTサーバを構築する際に別コンテナにloalhostで接続できるようにする

### TL;DR
- Mosquittoを使ってMQTTサーバを立てた際にコンテナ間でlocalhostで接続できないかと調査
- docker-composeでnetwork_modeを「host」にすることでホスト端末と同じIPアドレスにすることで可能とわかった
- GO言語のpaho.mqtt.golangライブラリを使って接続テストを実施

### 環境
- Windows10
- Docker Desktop 2.2.0.3
- docker-composeはDocker Desktopに同梱
- visual studio code 1.42.1[拡張機能Remote - Containers使用]
- Mosquitto 1.6.9

### 完成したリポジトリ
https://github.com/MegaBlackLabel/mqtt-docker-sample

### ファイル
```yaml:docker-compose.tml
version: '3'

services:
    api:
        build:
            dockerfile: Dockerfile
            context: ./containers/api
        volumes:
            - ./containers/api/src:/go/api
        tty: true
        network_mode: "host"
    mqtt:
        build:
            dockerfile: Dockerfile
            context: ./containers/mqtt
        ports: 
            - "1883:1883"
        volumes:
            - mosquittodata:/mosquitto/data
            - mosquittolog:/mosquitto/log
        tty: true
        network_mode: "host"

volumes:
    mosquittodata:
        driver: "local"
    mosquittolog:
        driver: "local"
```

- apiコンテナとmqttコンテナにnetwork_modeで「host」を設定することでホスト端末と同じIPアドレスを使用する


```golang:main.go
package main

import (
	"crypto/tls"
	"flag"
	"fmt"
	"os"
	"strconv"
	"time"

	MQTT "github.com/eclipse/paho.mqtt.golang"
)

// Que Strut.
type Que struct {
	Server    string
	Sendtopic string
	Resvtopic string
	Qos       int
	Retained  bool
	Clientid  string
	Username  string
	Password  string
	Client    MQTT.Client
	Callback  MQTT.MessageHandler
}

// Connect func .
func (q *Que) Connect() error {
	connOpts := MQTT.NewClientOptions().AddBroker(q.Server).SetClientID(q.Clientid).SetCleanSession(true)
	if q.Username != "" {
		connOpts.SetUsername(q.Username)
		if q.Password != "" {
			connOpts.SetPassword(q.Password)
		}
	}
	tlsConfig := &tls.Config{InsecureSkipVerify: true, ClientAuth: tls.NoClientCert}
	connOpts.SetTLSConfig(tlsConfig)

	client := MQTT.NewClient(connOpts)
	if token := client.Connect(); token.Wait() && token.Error() != nil {
		return token.Error()
	}
	q.Client = client

	if q.Callback != nil {
		if token := q.Client.Subscribe(q.Resvtopic, byte(q.Qos), q.Callback); token.Wait() && token.Error() != nil {
			return token.Error()
		}
		fmt.Printf("[MQTT] Subscribe to %s\n", q.Sendtopic)
	}

	fmt.Printf("[MQTT] Connected to %s\n", q.Server)

	return nil
}

// Publish func .
func (q *Que) Publish(message string) error {
	if q.Client != nil {
		token := q.Client.Publish(q.Sendtopic, byte(q.Qos), q.Retained, message)
		if token == nil {
			return token.Error()
		}
		fmt.Printf("[MQTT] Sent to %s\n", q.Sendtopic)
	}

	return nil
}

// SetSubscribe - .
func (q *Que) SetSubscribe(callback MQTT.MessageHandler) error {
	if callback != nil {
		if token := q.Client.Subscribe(q.Resvtopic, byte(q.Qos), callback); token.Wait() && token.Error() != nil {
			return token.Error()
		}
		fmt.Printf("[MQTT] Subscribe to %s\n", q.Sendtopic)
	}

	return nil
}

func onMessageReceived(client MQTT.Client, message MQTT.Message) {
	fmt.Printf("[MQTT] Received to %s [Received Message: %s]\n", message.Topic(), message.Payload())
}

func main() {
	hostname, _ := os.Hostname()

	server := flag.String("server", "tcp://localhost:1883", "The full URL of the MQTT server to connect to")
	sendtopic := flag.String("sendtopic", "MQTT/Client/Update/TEST", "Topic to publish the messages on")
	resvtopic := flag.String("resvtopic", "MQTT/+/Update/#", "Topic to publish the messages on")
	qos := flag.Int("qos", 0, "The QoS to send the messages at")
	retained := flag.Bool("retained", false, "Are the messages sent with the retained flag")
	clientid := flag.String("clientid", hostname+strconv.Itoa(time.Now().Second()), "A clientid for the connection")
	username := flag.String("username", "", "A username to authenticate to the MQTT server")
	password := flag.String("password", "", "Password to match username")
	flag.Parse()

	q := &Que{
		Server:    *server,
		Sendtopic: *sendtopic,
		Resvtopic: *resvtopic,
		Qos:       *qos,
		Retained:  *retained,
		Clientid:  *clientid,
		Username:  *username,
		Password:  *password,
		Callback:  onMessageReceived,
	}

	err := q.Connect()
	if err != nil {
		fmt.Println(err)
		os.Exit(2)
	}

	if err := q.SetSubscribe(onMessageReceived); err != nil {
		fmt.Println(err)
		os.Exit(2)
	}

	for {
		time.Sleep(5000 * time.Millisecond)
		if err := q.Publish("test massage"); err != nil {
			fmt.Println(err)
			os.Exit(2)
		}
	}
}
```

- MQTTサーバの接続先に「tcp://localhost:1883」を指定しているが、お互いのコンテナがnetwork_modeで「host」を設定しているので接続できる

### MQTT接続実行結果
```shell:実行結果
root@docker-desktop:/go/api# go run main.go 
go: downloading github.com/eclipse/paho.mqtt.golang v1.2.0
go: downloading golang.org/x/net v0.0.0-20200320220750-118fecf932d8
[MQTT] Subscribe to MQTT/Client/Update/TEST
[MQTT] Connected to tcp://localhost:1883   
[MQTT] Subscribe to MQTT/Client/Update/TEST
[MQTT] Sent to MQTT/Client/Update/TEST
[MQTT] Received to MQTT/Client/Update/TEST [Received Message: test massage]
[MQTT] Sent to MQTT/Client/Update/TEST
[MQTT] Received to MQTT/Client/Update/TEST [Received Message: test massage]
[MQTT] Sent to MQTT/Client/Update/TEST
[MQTT] Received to MQTT/Client/Update/TEST [Received Message: test massage]
```

### まとめ
MQTTサーバをDockerで構築する際にサーバをlocalhostにできないかな、というので調査していてnetwork_mode使えばできることがわかったので記事にしてみました。