DockerでMQTT通信

スポンサーリンク

はじめに

異なる機器間の通信を”トピック”と”メッセージ”という抽象化された概念でやり取りできるMQTTはとても便利ですよね。
PythonにもPaho-MQTTという便利なMQTT Clientライブラリがあり、これを使うことで簡単にMQTT通信ができます。
しかし、Pythonを使っていると複数バージョンの環境を構築する必要が生じるなど、何かと環境設定が煩雑になりがちではないでしょうか。こういった環境はDockerのコンテナごとにバージョンや用途を切り分け、ローカル環境を汚さないようにする使い方はわりと一般的ではないかと思います。
MQTT通信を試してみよう!」の記事でパソコンとスマホを使ったMQTT通信の紹介をしましたが、こういった環境をDockerの仮想環境内で閉じておきたい方のために、その実施例を紹介します。
下図の環境を構築し、ローカルエリア内で複数のパソコンおよびスマホ間でMQTT通信を行う方法を説明します。

Dockerのインストール

Dockerをインストールするには以下URLからアカウント作成、インストールを行います。
https://docs.docker.com/install/
Dockerについては、過去の記事も参考にしてください。

Gitのインストール

Macでは最初からgitが使えるので新たにインストールする必要はありませんが、Windowsでは以下からgitをインストールしておくと便利です。
https://gitforwindows.org/

動作環境

以下の環境で動作確認しました。

使用機器OS使用ツール
MacmacOS CatalinaDocker for Mac 2.1.0.5
WindowsWindows10Docker for Windows 2.1.0.5
スマホAndroid 8.0.0MQTT Dash

Docker用ファイルのダウンロード

Macではターミナル、WindowsではPowerShellなどのコマンドラインツールを立ち上げ、作業場所まで移動(cd)してから以下のgitコマンドで必要なファイルをGitHubからダウンロードします。
Mac側とWindows側とも同じものをダウンロードすることで良いです。

git clone https://github.com/tak6uch1/paho-mqtt

以降の説明において、MacとWindowsは役割を入れ替えることもできますし、両方Macあるいは両方Windowsにすることもできますので、適宜読み替えて頂ければ結構です。

Mac側(Broker & Client 1)の環境構築および動作確認

ダウンロード(git clone)したファイルのうち、docker-compose.ymlは以下であり、全体の構成を表しています。
このファイルは特に編集しなくても良いです。

version: "3.4"
services:
  mosquitto:
    image: eclipse-mosquitto
    restart: always
    ports:
      - "1883:1883"
    volumes:
      - ./mosquitto/config:/mosquitto/config
      - ./mosquitto/data:/mosquitto/data
      - ./mosquitto/log:/mosquitto/log
    entrypoint:
      - mosquitto
    networks:
      mqtt_net:
        ipv4_address: 172.19.0.11

  paho:
    build: paho
    container_name: paho
    command: /usr/sbin/sshd -D
    ports:
      - "49944:22"
    volumes:
      - type: bind
        source: ./work
        target: /work
    networks:
      mqtt_net:
        ipv4_address: 172.19.0.12

networks:
  mqtt_net:
    ipam:
      config:
        - subnet: 172.19.0.0/16

コンテナはmosquitto(Broker)とpaho(Client)の2つで、mqtt_netというDocker内のネットワークに共に接続しています。
mosquittoのイメージは、既存のeclipse-mosquittoをそのまま使いますので、Dockerfileは用意していません。
pahoの方はpython:3.6のイメージに対し、開発環境でよく使うものをインストールしているのと、ある程度の使いやすさを考慮した設定を行っています。これらは適宜お好みに合わせて編集して構いません。
mosquittoとpahoにはそれぞれ固定のIPアドレスを割り当てています。

Clientの設定ファイルであるpaho/Dockerfileは以下のようになっており、4行目のyour_passwordを好きなパスワードに変更しておきます。

FROM python:3.6
ARG USER=user
ARG GROUP=developer
ARG PASS=your_password

ENV DEBIAN_FRONTEND noninteractive
ENV DEBCONF_NOWARNINGS yes

# Common
RUN apt-get update && apt-get install -y \
  apt-transport-https \
  libasound2-dev \
  bash-completion \
  build-essential \
  bzip2 \
  cmake \
  curl \
  gcc \
  g++ \
  git \
  less \
  libatlas-base-dev \
  libgl1-mesa-dev \
  locales \
  locales-all \
  make \
  man \
  manpages-dev \
  mosquitto-clients \
  net-tools \
  openssh-server \
  openssh-client \
  p7zip \
  postgresql-client \
  software-properties-common \
  sudo \
  unzip \
  vim \
  wget \
  xorg-dev \
  zlib1g-dev \
  zsh

RUN groupadd -g 1000 $GROUP
RUN useradd -g $GROUP -G sudo -m -s /bin/bash $USER

RUN mkdir /var/run/sshd
RUN echo "${USER}:${PASS}" | chpasswd
RUN echo "root:${PASS}" | chpasswd

# SSH settings. Otherwise user is kicked off after login
RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
RUN sed -e 's@#Port 22@Port 22@' -e 's@#AddressFamily any@AddressFamily inet@' -i /etc/ssh/sshd_config

# Locale
ARG LANGVAL=en_US.utf8
RUN echo "export LANG=$LANGVAL" >> /etc/profile
RUN echo "export LANGUAGE=$LANGVAL" >> /etc/profile
RUN echo "export LC_CTYPE=$LANGVAL" >> /etc/profile
RUN echo "export LC_ALL=$LANGVAL" >> /etc/profile
ENV LANG     $LANGVAL
ENV LANGUAGE $LANGVAL
ENV LC_CTYPE $LANGVAL
ENV LC_ALL   $LANGVAL
RUN echo "$LANGVAL UTF-8" > /etc/locale.gen && \
    locale-gen && \
    update-locale LANG=$LANGVAL

RUN cp /usr/share/zoneinfo/Japan /etc/localtime

# paho-mqtt
RUN pip3 install paho-mqtt
#### User ####
USER $USER
WORKDIR /home/$USER

# Settings
RUN echo 'alias ls="ls -a --color=auto --show-control-chars --time-style=long-iso -FH"' >> /home/$USER/.profile
RUN echo 'alias ll="ls -a -lA"' >> /home/$USER/.profile
RUN echo 'alias h=history' >> /home/$USER/.profile
RUN echo 'alias vi=vim' >> /home/$USER/.profile

RUN echo 'PS1="\$ "' >> /home/$USER/.bashrc
RUN touch /home/$USER/.Xauthority && chmod 600 /home/$USER/.Xauthority

USER root

ADD .vimrc /home/$USER/.vimrc
ADD .gvimrc /home/$USER/.gvimrc
RUN chown -R $USER:$GROUP /home/$USER/.*vim*
RUN chmod -R 644 /home/$USER/.*vim*

EXPOSE 22
EXPOSE 8080

上記ファイル内で変更したパスワードはSSHログインする際に必要になりますので覚えておいてください。
では、ビルドを行います。

docker-compose build

正常にビルドが終了したら、コンテナを立ち上げ、ClientにSSHログインします。ここで上記で変更したパスワードを使います。

docker-compose up -d
./run_ssh.sh

※Windowsの場合は./run_ssh.batを使います。

再ビルドしたりするとssh接続時にエラーが出るので、以下のようにリフレッシュすると良いです。(他にも方法はありますが、説明は他のサイトにお任せします)

rm ~/.ssh/known_hosts

ClientへのSSHログインに成功したら、以下のように作業ディレクトリまで移動し、lsを実行すると以下のように2つのファイルが表示されると思います。

cd /work/mqtt
ls
./             ../            publisher.py   subscriber.py

ここでSubscriberを立ち上げます。

python subscriber.py

すると次のように表示されて何も起きないと思いますが、これで問題ありません。

status 0

Paho-MQTTのソースコードはこちらのページを参考にさせて頂きました。
subscriber.pyの内容は下記の通り、test/messageというトピックのメッセージを受け取るとその内容を表示するプログラムになっています。

# -*- coding: utf-8 -*-
import paho.mqtt.client as mqtt

HOST = '172.19.0.11'
PORT = 1883
KEEP_ALIVE = 60
TOPIC = 'test/message'

def on_connect(client, userdata, flags, respons_code):
    print('status {0}'.format(respons_code))
    client.subscribe(client.topic)

"""
def on_message(client, userdata, message):
topicを受信したときに実行する
"""
def on_message(client, userdata, message):
    print(message.topic + ' ' + str(message.payload))

if __name__ == '__main__':

    client = mqtt.Client(protocol=mqtt.MQTTv311)
    client.topic = TOPIC

    client.on_connect = on_connect
    client.on_message = on_message

    client.connect(HOST, port=PORT, keepalive=KEEP_ALIVE)

    # ループ
    client.loop_forever()

次に別のターミナルを立ち上げ、同様にSSHログインして上記と同じ作業ディレクトリまで移動します。
この新しいターミナルからPulish(メッセージの送信)をしてみます。

python publisher.py

これで、先に立ち上げたSubscriberのターミナルに以下のようにhelloのメッセージが表示されればMQTT通信ができています。

test/message b'hello'

ただし、これは1つのDocker環境内に構築した同一ネットワーク(mqtt_net)につながる2つのClient間の通信に成功したに過ぎません。
ここでは単に確認のために同一ネットワーク内の通信を試しましたが、本来やりたかったのは他の機器との通信であるため、それを次節で行います。

publisher.pyの内容は以下で、test/messageというトピックでhelloというメッセージを送ります。

import paho.mqtt.client as mqtt

HOST = '172.19.0.11'
PORT = 1883
KEEP_ALIVE = 60
TOPIC = 'test/message'
MESSAGE = 'hello'

if __name__ == '__main__':
    client = mqtt.Client(protocol=mqtt.MQTTv311)
    client.connect(HOST, port=PORT, keepalive=KEEP_ALIVE)

    client.publish(TOPIC, MESSAGE)
    client.disconnect()

次節でこのパソコンのIPアドレス(DockerネットワークのIPアドレスとは異なる)を使用するため、調べておきます。

ifconfig

inet 192.168.179.2 (私の場合)のように表示されたものが、このパソコンのIPアドレスになります。
※Windowsの場合はipconfigコマンドでIPアドレスを調べます。

※BrokerをWindows上に立ち上げる際、ファイアーウォールに阻まれて他のマシンからのPublishメッセージをBrokerが受信できなかったので、このページを参考に受信の規則にTCP 1833ポートをプライベートネットワークに対して接続許可し、再起動することでうまく通信できるようになりました。

Windows側(Client 2)の環境構築および動作確認

同様にpaho/Dockerfile内のパスワードを変更します。
こちらはBrokerが必要ありませんので、ビルドとコンテナ立ち上げは、pahoのみとし、SSHログインまで実施します。

docker-compose build paho
docker-compose up -d paho
./run_ssh.bat

※Macの場合は、./run_ssh.shを使います。
SSHログインに成功したら、同様に作業ディレクトリに移動します。

cd /work/mqtt

ここで、今度はもう一方のパソコンにPublishしたいので、publisher.pyの3行目HOST = ‘172.19.0.11’の部分を先ほど調べたもう一方のパソコンのIPアドレスに書き換えます(私の場合、HOST = ‘192.168.179.2’)。
IPアドレスを書き換えたらPublishします。

python publisher.py

これでもう一方のパソコンのSubscriberのターミナルに以下のメッセージが出ればパソコン間のMQTT通信は成功です!

test/message b'hello'

スマホ(Client 3)との通信

AndroidスマホのアプリMQTT Dashを使ってBrokerに接続します。
MQTT通信を試してみよう!」と同様の内容ですので、ポイントのみ説明します。
まず、以下のようにName, Address, Portを設定します。
Addressは先ほどifconfigやipconfigで調べたBrokerのIPアドレス、Portは1883とします。

続いて、チェックボックスを作ります(例)。トピックは前節と同じtest/messageとしておき、チェックを入れると1を、チェックを外すと0を送るようにします。

 できたチェックボックスをタップしてチェックを入れたり、チェックを外したりしてみます。

パソコンの Subscriberのターミナルに以下のようにメッセージが表示されれば、スマととパソコン間のMQTT通信も成功です!

test/message b'1'
test/message b'0'

参考文献

タイトルとURLをコピーしました