Skip to main content

Logicool MX Anywhere 2S(MX1600s)購入

マウスを買ったので写真を撮りました。というエントリです。

おしごと用のマウスは、2012 年から Logicool の Anywhere Mouse(M905r)を愛用しています。数年おきにチャタリングが起きるようになっていましたが、都度交換してもらって、保証期間をだいぶ過ぎた今でも現役です。

一方、自宅用のマウスは、気分でいろいろとっかえひっかえしており、とくにコレと決めたものはありませんでした。が、最近チャタリングが気になりだしたので、慣れたものに決めてしまおうと、おしごと用の M905r の後継である MX Anywhere 2S(MX1600s)を購入したわけです。

これです。

MX1600s
MX1600s

かわいいですね。

並べてみましょう。M905r はつるっとしていましたが、MX1600s ではマットな仕上げに変わっています。かっこうよいですね。

M905r(左)と MX1600s(右)

また、M905r は乾電池式でしたが、MX1600s は、USB で給電する充電池式です。電池の寿命を無視できるのはうれしいですね。何年でヘタるかはわかりませんが……。

MX1600s(右)はマウスの頭に充電用の USB ポートがある

でも、実は不満もあるのです。

M905r は、電池の蓋をあけたところに Unifying レシーバを格納できましたが、MX1600s ではこれができません。

M905r は電池蓋を開けると Unifying レシーバを格納できた

とはいえ、MX1600s は例の Logicool Flow に対応しているので、Unifying レシーバを差し替えて複数台で使いまわすようなこと自体が、もはや本来の想定された使い方ではないということなのでしょう。たぶん。

しかし、それでも、それでもまだ不満はあるのです。

M905r はスイッチのオンオフが信じられないくらいラクだったのです。なぜなら物理的にスイッチが大きくて操作しやすいからで、オフィス内をマウスとともにうろうろする前後で片手でスイッとできていました。

M905r(左)と MX1600s(右)でスイッチのサイズが圧倒的に違う
両モデルでスイッチを入れたところ

MX1600s は、スイッチがほんとうに小さくて、爪が短いと操作しづらいレベルです。ぼくの使い方だとユーザビリティが著しく悪化したポイントです。

また、M905r に付属していたソフトケースが無くなったのも個人的には非常に痛いところです。おしごと用の M905r は、鞄に放り込んでの持ち運びを散々しているので、ソフトケースが大活躍だったのでした。

使い古されてきた M905r のソフトケース
専用のソフトケースなので、サイズもぴったり

というわけで、最新世代たる MX1600s は、モバイル用を謳った後継機ではあるものの、ぼくにとっては一部不満が残ってしまうものでした。

でもなんだかんだいいながらも、マウスとして一番だいじな実際の使い心地は、相変わらず抜群に快適です。この辺の品質に妥協がないのは安心感があります。また、サイズ感もまったく変わっていないので、何の違和感もなく一瞬で馴染みました。

ソフトケースはいざとなったら M905r のを今後も使えばいいし、素直に Logicool Flow をセットアップすれば Unifying レシーバを持ち歩く必要もなくせるでしょう。スイッチの形状だけはどうしようもないですが、きっと慣れます。

よいマウスです

以上、おすすめマウスの紹介でした。

今回、撮影ボックスを初めて使ってみました。ライティングはもう少し工夫したいですし、こういう写真のときの被写界深度の考え方はお勉強が必要です。


おうち IoT 用の LINE ボットをもう少し賢くする

これまでの LINE ボットの課題

2018 年に、家のエアコンを操作してくれる LINE ボットを作って以来、すでに一年半くらい運用しています。概要は 当時のエントリでも紹介しています が、LINE での会話を元に自宅のエアコンを操作してくれるものです。

これ、作った当初に想定していた以上にたいへん便利で、外出先で家族と『そろそろロボくんにお願いしておこう』などの会話が発生する程度には実際に活用されていました。この手の『作ってみた』系は、長期的な運用が定着する前に使わなくなることが多い印象もあり、これは小規模ではあるもののうまくいった例と言えるかと思います。

当時の LINE ボットの様子

一方で、作りが甘い部分もあって、

  • Lambda の Node.js のランタイムで EoL が迫っていた
    • そもそも Node.js のランタイム側の更新に追従していくことに将来的にけっこう体力を使いそうな印象がある
    • 当時 Node.js を選んだ理由は『とりあえずいちど触ってみたかった』からというだけで、すでにその目的は達成できた
  • ひとつの Lambda に全機能を詰め込んでいて、どう考えてもイケてないアーキテクチャだ
  • 機能が足りない
    • エアコンのオンオフと温度の変更はできたが、冷房と暖房の切り替え機能を実装していない

など、長期的な運用に耐えられるよう全体を作り直したいモチベーションも強くなってきていました。

そんな中、あるイベントに参加して、『Raspberry Pi とセンサとクラウドサービスを使って何でもいいから個人で何かを作る』という活動をすることになり、タイミングもよかったので、この LINE ボットの作り直しを進めることにしました。

できたもの

で、こういう風に進化して、今も元気に動いています。

現在の LINE ボットの様子

以下のような機能が付いています。

  • エアコンの操作
    • オンオフ、冷暖房の切り替え、温度の変更、現在の設定の確認をしてくれます
  • 加湿器の操作
    • オンオフをしてくれます
  • 空調センサ
    • 室内に設置したセンサを使って、気温、湿度、気圧、二酸化炭素濃度の現在の値を教えてくれます
    • 任意の時間分の履歴をグラフ化して送ってくれます
  • 家計簿の記録
    • 支払者、使途、金額を Google Sheets に追記してくれます
  • 毎朝の自動制御
    • 毎朝、部屋が寒い(暑い)場合は、暖房(冷房)を付けてくれます
    • 毎朝、部屋が乾燥している場合は、加湿器を付けてくれます

実装

実装を簡単に紹介します。末尾には GitHub へのリンクも載せています。

全体像

全体はこのような構成です。

こけおどし全体像

参加したイベントの性質上、なるべくたくさんの要素技術を取り込んだほうが評価が上がる仕組みだったため、あえてまわりくどく冗長な構成になっている部分もあります。

センシングと蓄積

センシングと蓄積は、図の以下の部分です。これまで複数回にわたって紹介してきた EdgeX Foundry を中心に構成しています。

センシングと蓄積の部分

EdgeX Foundry 自体は、家庭内の IoT ゲートウェイサーバとして構築した Ubuntu 仮想マシン(ESXi 上で動作)の中で動いています。このゲートウェイサーバ上では MQTT ブローカも動かしています。

Raspberry Pi は、センサ値を読んで MQTT トピックにひたすら放り込み続けるだけの係です。EdgeX Foundry は、この Raspberry Pi を MQTT デバイスとして制御するよう構成されており、所定の MQTT トピックに投げ込まれたデータを取り込んで、アプリケーションサービスの機能を使ってクラウド上の所定の宛先にデータをエクスポートしています。

エクスポート先は、Mosquitto のパブリック MQTT トピックと、Pivotal Web Service(PWS)上のワーカアプリケーションで、なんやかんやされて最終的にデータは InfluxDB Cloud 2.0、Redis Cloud、MongoDB mLab に蓄積されます。

十数年前に PIC や Arduino でセンサやアクチュエータを操作していた頃は、データシートとにらめっこをしながら必要な抵抗を計算したり回路を考えたりしていましたが、最近のヤツは単にデータを読み取るだけなら恐ろしいくらい簡単ですね…… けっこう衝撃でした。

今回は BME280 と MH-Z19 を使っています。安いですし精度は悪いのでしょうが、厳密な値は必要としていないのでよしとしています。最初は DS18B20 も使っていましたが、BME280 を組み込んでからは使っていません。

アクチュエーション

最終的にはエアコンと加湿器が操作されるわけですが、この部分の実装は、前者は Nature Remo の API、後者は非公式の野良 API で制御しています。

アクチュエーションの部分

Nature Remo の API を叩く役は、AWS の所定の Lambda に一任しています。加湿器も同様ですが、こちらは非公式 API の都合上、ローカルネットワーク内からしか制御できなかったので、Lambda からいちど IoT Core の MQTT トピックに命令をなげ、それをローカルネットワーク側から購読して後続の処理をトリガさせています。ここだけは Node.js です。

加湿器は、モデルの選定がなかなかたいへんでした。加湿器に限らず何らかの家電を外部から自動制御する場合、

  • スマートコンセントを利用する
    • コンセントが通電したら目的の動作が開始される仕様である必要がある
  • スマートスイッチ(SwitchBot など)を利用する
    • 目的のボタンが、物理的にスマートスイッチで押下できる形状であり、かつスマートスイッチの力で押せるだけの硬さである必要がある
  • スマートリモコンを利用する
    • RF ではなく赤外線を利用したリモコンで制御できる機器である必要がある
  • 専用の API を利用する
    • API が公開されている必要がある

のいずれかの条件を満たす必要がありますが、加湿器でこれに合致するモデルが全然見つけられませんでした。大体の加湿器は、コンセントの通電後にさらにボタンを押さないと動作しませんし、ボタンの周辺の形状や寸法やボタンの硬さは公開されていませんし、リモコン付きのモデルでも赤外線でなく RF ですし、公式の API はなさそうですし。

結局、公開されていない API を無理やり叩ける非公式のライブラリが存在していてハックの余地がありそう、ということがわかった Oittm のアロマディフューザー を採用しています。ただしこれも、

  • 非公式のライブラリ で制御できるようにするには、公式アプリケーション Tuya Smart の旧バージョン(3.12.6 以前)を使って、MITM 攻撃的なヤツでアプリケーションの通信を自分で盗聴してキーを入手しないといけない
  • ライブラリが Node.js 向けしかない
  • そもそもの加湿器自体が小型でとても非力で、リビングなど広い空間の加湿には向かない

など、ベストとは言い難い状態です。改善の余地ありです。

インタフェイスとインタラクション

インタフェイス役の LINE ボットに話しかけると、LINE の Messaging API 経由で AWS の Lambda に届いて、メッセージ本文がパタンマッチされてその内容に応じて後続の処理がトリガされます。

インタラクションの部分

エアコンの操作であれば Nature Remo を制御する Lambda を呼びますし、加湿器の操作であればそれ用の別の Lambda を呼びます。センサのデータが必要な処理であれば、外部のデータベースに必要なデータを取りに行く Lambda を呼びます。

インタフェイスとしての LINE

グラフを要求された場合は、InfluxDB からデータを取ってきて matplotlib で描画したあとに S3 に保存し、それをプッシュでユーザに送ります。描画する対象(温度、湿度、気圧……)と長さ(N 分、M 時間)はメッセージ本文から都度判断します。

実際に生成されたグラフの例

この辺りの処理では、

  • グラフ要求のメッセージが来たら、受理した旨の返事を後続処理に先行して即座にしてしまい、画像の生成と送信はあとから非同期で行う
    • グラフの生成に時間がかかるため、 同期的に処理させると(Lambda ではなく)API Gateway がタイムアウトする
  • S3 のバケット名や画像ファイル名をなるべく短くする
    • LINE で画像を送るときは、画像そのものではなく画像の URL を送る必要がある
    • Lambda で発行する S3 の署名付き URL は 1,000 文字前後とめちゃくちゃ長い(x-amz-security-token が含まれるため)
    • LINE 側の制約で、URL は 1,000 文字以内でないとエラーで送れない
    • 署名付き URL ではなくパブリックな URL にしてしまうのは気が引ける
    • バケット名やファイル名は URL に含まれるため、短くすることでギリギリ 1,000 文字以内になる

などの工夫が必要でした。特に二点目はひどい回避策ですが、あまり権限をがばがばにはしたくなかったので仕方なく……。

また、LINE とはまったく関係ないところで、Grafana を PWS で動かしており、そこでもグラフを見られるようにしています。

感触とロードマップ

センサの値がグラフで見られるのが、実際に使ってみると思っていた以上におもしろかったです。

例えばエアコンをつけてから温度が上がっていく様子や、朝起きて部屋で人間が活動しだしてから二酸化炭素濃度が一気に上がる様子、逆に家が無人になってから下がる様子、キッチンで火を使う調理を始めたタイミングなど、思っていた以上に生活パタンが可視化されることがわかりました。

また、二酸化炭素濃度や気圧の変化では眠気や頭痛の誘発なども懸念されるので、体調に違和感を覚えたときに客観的な変化をすぐ確認できるのはうれしく、体調管理面でも地味に役立っています。

運用面では、Lambda まわりをすべて CloudFormation で定義したことで、コードの管理とデプロイがだいぶに楽になりました。クラウド側がマネージドサービスとサーバレスアーキテクチャだけで組めているのも安心感があります。

ただ、本エントリの途中で、

参加したイベントの性質上、なるべくたくさんの要素技術を取り込んだほうが評価が上がる仕組みだったため、あえてまわりくどく冗長な構成になっている部分もあります

と書いた通り、正直なところ機能に比較して実装が大げさすぎるので、この辺はスリムにしたいと思っています。

イベントはもう終わったので、現状のゴテゴテ感を保つ意味はあまりなく、例えば、

  • この規模だと EdgeX Foundry の恩恵は受けにくいので、まるっと削って Raspberry Pi から直接クラウドに投げてもよいだろう
  • データの蓄積場所も AWS の DynamoDB などにしてしまえば、PWS も InfluxDB も Redis も MongoDB もいらなくなり、AWS に全部寄せられるだろう
  • いっそ Raspberry Pi も AWS IoT Greengrass で AWS から管理させるとよいのでは

などのダイエットや試行錯誤を検討中です。とくに Greengrass は完全に未修分野なので純粋に興味もあり触ってみたいですね。

物理的には、加湿器をもう少し高機能(大容量)かつ自動制御しやすいものに替えたい気持ちがあります。オンオフは自動で制御できても、給水が自動化しづらいので、結局は人間の介入が必要な状態になってしまっており、人間が手を抜くにはタンクの容量がキモになりそうだと考えています。

関連リポジトリ

一覧します。

  • raspi-airmeasurment
    • Raspberry Pi 上で動作させる、各種センサ値を読み取って MQTT トピックに送る Python プログラム
  • edgex-lab-raspi
    • Raspberry Pi から MQTT トピックに送られた値の取り込みや、クラウドへのエクスポートを行えるように構成した EdgeX Foundry の Docker Compose ファイル
  • edgex-lab-export2db
    • PWS 上で動作させる、EdgeX Foundry からエクスポートされたデータを各種データベースに保存するためのプログラム群
    • Telegraf 用のイメージは Docker Hub の kurokobo/edgex-lab-telegraf に配置済み
  • grafana-with-flux
    • PWS 上で動作させる Grafana
  • sam-smarthome-api
    • AWS 上で動作させる一連の Lambda 群
  • tuya-mqtt
    • MQTT の配信を受けて加湿器をコントロールする Node.js プログラム


ゲームボーイアドバンスの液晶を交換した話

高校二年生のとき、中学生の頃に買ったゲームボーイアドバンスを、初めていろいろ改造した。

ホワイトだった本体をウレタン塗料でがっつり黒に塗り、電源の LED を緑から青に変え、そして Afterburner と呼ばれるフロントライトを追加して、さらにその光量調節をボタン操作で行えるようにするチップ(Stealth Dimmer Chip)を取り付け。

今では考えられないけれど、ゲームボーイアドバンス(というか当時の携帯ゲーム機全般)はもともと光源を何も積んでおらず、致命的に画面が暗かった。

だからこのフロントライトも、当時はけっこうインパクトがあり、わくわくしながら取り付けたものである。実際、暗所でもこれだけの視認性が得られる。

で、それから 15 年ちかく経った最近、ゲームボーイアドバンス用の交換用のバックライト付き液晶の存在を知った。もちろん純正品ではない。

とくに遊びたいゲームがあったわけではないのだけれど、久しぶりに何かを改造したい欲がもこもこしてきたので、とりあえずポチり、台風で家に引きこもっている最中にちまちまと交換したという具合。

まずは前述の Stealth Dimmer Chip を取り外さないといけない。この半田付け、高校生のぼくにはものすごく難易度が高くて苦労した記憶がある。今見ると配線はキレイだけれど、半田付けがものすごく汚い……。イモもいいところである。とはいえ、15 年動いてくれたわけだけれど。

これがもともと付けていた Afterburner。雑に言えば、液晶の手前に配置する、光る透明な板。

こんな感じで光る。

懐かしさを覚えつつ、全部とっぱらって、左のモノに交換。

試しに通電させた時点で、技術の進歩ってすごいなあと思った。明るいし…… ドットが細かいし……。

この液晶、解像度が元の 4 倍で、従来の 1 ドットを 2 × 2 の 4 ドットで描画している。ドットピッチが倍なので、単に明るいだけでなく、明らかに見た目がきれい。

ただし、元の液晶よりも一回り大きいので、本体のケースをごりごり物理的に削らないと所定の位置に収まらない。

単に部品を交換するだけだとあっさりすぎるので、改造している感があってちょうどよかった(?)。

で、こうなりました。

とても明るい。そしてきれい。暗所でこれだけ見えて恐ろしい。コントラストがエグい。

並べると圧倒的な差。満足です。


Docker 上で bind を動かす

おさらい

前回までのエントリでは、Photon OS をデプロイして初期設定を行いました。

もともとの目的は、vCenter Server Appliance 6.5(vCSA 6.5)をお行儀のよい構成でインストールするために必要な DNS サーバを作ることです。よって今回は、前回構築した Photon OS 上で、bind をサービスする Docker コンテナを作ります。

設計

こんな感じにします。

  • ベース OS は Alpine Linux にする
  • bind を追加する
  • bind の設定ファイル群はホスト OS 上のディレクトリに置き、コンテナからマウントさせる
  • Docker Compose で起動・停止できるようにする

ベース OS の選択には特にこだわりはないので、流行に乗って Alpine Linux にします。最新版を使います。

設定ファイルの置き場所は悩みどころですが、設定の変更などがしやすくなるのでこうしました。ポータビリティは下がりますが、配布予定もないし単一のサーバでしかホストしないのでよしとします。

Docker Compose は蛇足です。本来は複数のコンテナを連携させるときに使うものですが、使ってみたかったので使ってみます。

以降、やっぱり文字ばかりになるので、きれいな孔雀の写真を載せておきます。配色が Photon OS のそれと似ていますね。

Dockerfile を作る

まずは Dockerfile です。作ります。コンテナイメージのレシピみたいなものです。

kuro@kuro-ph01 [ ~ ]$ vim Dockerfile
kuro@kuro-ph01 [ ~ ]$ cat Dockerfile
FROM alpine:latest
RUN apk add bind --no-cache
EXPOSE 53/udp
CMD ["/usr/sbin/named", "-c", "/etc/bind/named.conf", "-u", "named", "-g"]

bind の設定ファイル群を作る

コンテナの中で動かす bind のための設定ファイルを作ります。

メインの named.conf と、ゾーン定義の二つのファイルを作りました。作ったファイルはひとつのディレクトリにまとめておきます。コンテナの中の named ユーザが読み取れるように、パーミッションも変更します。

kuro@kuro-ph01 [ ~ ]$ mkdir conf
kuro@kuro-ph01 [ ~ ]$ vim conf/named.conf
kuro@kuro-ph01 [ ~ ]$ cat conf/named.conf
acl localnet {
    127.0.0.1;
    192.168.0.0/24;
};

options {
    version "unknown";
    directory "/var/bind";
    pid-file "/var/run/named/named.pid";
    recursion yes;
    notify no;

    listen-on { any; };
    listen-on-v6 { none; };

    allow-query { localnet; };
    allow-query-cache { localnet; };
    allow-recursion { localnet; };
    allow-transfer { none; };

    forwarders { 192.168.0.1; };
};

zone "kuro.local" IN {
    type master;
    file "/etc/bind/kuro.local.zone";
};
kuro@kuro-ph01 [ ~ ]$ vim conf/kuro.local.zone
kuro@kuro-ph01 [ ~ ]$ cat conf/kuro.local.zone
$TTL 1h
@ IN SOA ns.kuro.local postmaster.kuro.local. (
    2017030501 ; serial
    1h         ; refresh
    15m        ; retry
    1d         ; expire
    1h         ; minimum
);

@          IN NS ns.kuro.local.
ns         IN A  192.168.0.249
kuro-vc01  IN A  192.168.0.250
kuro-esx01 IN A  192.168.0.251
kuro@kuro-ph01 [ ~ ]$ chmod 755 conf
kuro@kuro-ph01 [ ~ ]$ chmod 644 conf/*

コンテナイメージをビルドして起動する

ここまで用意ができたら、あとはビルドして起動させるだけです。

まずはビルドします。ビルドは build サブコマンドです。末尾でビルドしたい Dockerfile を含むディレクトリを指定します。今回はカレントディレクトリなのでドット(.)です。

kuro@kuro-ph01 [ ~ ]$ docker build -t bind .
Sending build context to Docker daemon 11.78 kB
Step 1 : FROM alpine:latest
latest: Pulling from library/alpine
627beaf3eaaf: Pull complete
Digest: sha256:58e1a1bb75db1b5a24a462dd5e2915277ea06438c3f105138f97eb53149673c4
Status: Downloaded newer image for alpine:latest
 ---> 4a415e366388
Step 2 : RUN apk add bind --no-cache
 ---> Running in 84d3fb22724e
fetch http://dl-cdn.alpinelinux.org/alpine/v3.5/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.5/community/x86_64/APKINDEX.tar.gz
(1/5) Installing libgcc (6.2.1-r1)
(2/5) Installing libxml2 (2.9.4-r1)
(3/5) Installing bind-libs (9.10.4_p6-r0)
(4/5) Installing libcap (2.25-r1)
(5/5) Installing bind (9.10.4_p6-r0)
Executing bind-9.10.4_p6-r0.pre-install
Executing busybox-1.25.1-r0.trigger
OK: 9 MiB in 16 packages
 ---> 29500538ea16
Removing intermediate container 84d3fb22724e
Step 3 : EXPOSE 53/udp
 ---> Running in a8ea36f4c12f
 ---> f08f46db2947
Removing intermediate container a8ea36f4c12f
Step 4 : CMD /usr/sbin/named -c /etc/bind/named.conf -u named -g
 ---> Running in a2fd9c1acd90
 ---> bf1581067386
Removing intermediate container a2fd9c1acd90
Successfully built bf1581067386
kuro@kuro-ph01 [ ~ ]$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
bind                latest              bf1581067386        41 seconds ago      8.772 MB
alpine              latest              4a415e366388        2 weeks ago         3.984 MB

できたイメージは images サブコマンドで確認できます。無事にできたようなので動かしてみましょう。

起動は run サブコマンドです。バックグラウンドで起動(-d)させ、ホストのポートとディレクトリをコンテナに割り当て(-p、-v)ます。

kuro@kuro-ph01 [ ~ ]$ docker run -d -p 53:53/udp -v $(pwd)/conf:/etc/bind --restart always --name bind bind
442a5494f13328641cc3ba6c526a65591874f01476255a47f962dbc8922e0f3b
kuro@kuro-ph01 [ ~ ]$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
442a5494f133        bind                "/usr/sbin/named -c /"   3 seconds ago       Up 2 seconds        0.0.0.0:53->53/udp   bind

起動状態は ps サブコマンドで確認できます。この結果の STATUS が Up になっていれば、少なくともコンテナは正しく動いています。あとはコンテナ内で bind が正しく動いていれば問題ありません。

というわけで、まずは自分自身に DNS のクエリを投げてみます。ミニマル構成の Photon OS には nslookup も dig もないので、bindutils をインストールしてから試します。

kuro@kuro-ph01 [ ~ ]$ sudo tdnf install bindutils
[sudo] password for kuro
Sorry, try again.
[sudo] password for kuro

Installing:
bindutils                                            x86_64              9.10.4-1.ph1                            6.86 M

Total installed size: 6.86 M
Is this ok [y/N]:y

Downloading:
bindutils                              3116681    100%
Testing transaction
Running transaction

Complete!
kuro@kuro-ph01 [ ~ ]$ dig @192.168.0.249 kuro-vc01.kuro.local

; <<>> DiG 9.10.4-P1 <<>> @192.168.0.249 kuro-vc01.kuro.local
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 49190
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 2

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;kuro-vc01.kuro.local.          IN      A

;; ANSWER SECTION:
kuro-vc01.kuro.local.   3600    IN      A       192.168.0.250

;; AUTHORITY SECTION:
kuro.local.             3600    IN      NS      ns.kuro.local.

;; ADDITIONAL SECTION:
ns.kuro.local.          3600    IN      A       192.168.0.249

;; Query time: 0 msec
;; SERVER: 192.168.0.249#53(192.168.0.249)
;; WHEN: Tue Mar 21 00:04:46 JST 2017
;; MSG SIZE  rcvd: 98

kuro@kuro-ph01 [ ~ ]$ nslookup google.com 192.168.0.249
Server:         192.168.0.249
Address:        192.168.0.249#53

Non-authoritative answer:
Name:   google.com
Address: 172.217.25.238

定義したゾーンの情報も引けていますし、再帰問い合わせもできています。よさそうです。

別の端末からも叩いてみます。例えば Windows から。

C:\Users\kuro>nslookup kuro-vc01.kuro.local 192.168.0.249
サーバー:  UnKnown
Address:  192.168.0.249

名前:    kuro-vc01.kuro.local
Address:  192.168.0.250

C:\Users\kuro>nslookup google.com 192.168.0.249
サーバー:  UnKnown
Address:  192.168.0.249

権限のない回答:
名前:    google.com
Address:  216.58.197.142

よいですね。

うまく動かない場合は、以下のように logs サブコマンドで named のログを見るか、

kuro@kuro-ph01 [ ~ ]$ docker logs bind
20-Mar-2017 15:00:53.955 starting BIND 9.10.4-P6 <id:a6837d0> -c /etc/bind/named.conf -u named -g
20-Mar-2017 15:00:53.955 running on Linux x86_64 4.4.41-1.ph1-esx #1-photon SMP Tue Jan 10 23:46:44 UTC 2017
20-Mar-2017 15:00:53.955 built with '--build=x86_64-alpine-linux-musl' '--host=x86_64-alpine-linux-musl' '--prefix=/usr' '--sysconfdir=/etc/bind' '--localstatedir=/var' '--with-openssl=/usr' '--enable-linux-caps' '--with-libxml2' '--enable-threads' '--enable-filter-aaaa' '--enable-ipv6' '--enable-shared' '--enable-static' '--with-libtool' '--with-randomdev=/dev/random' '--mandir=/usr/share/man' '--infodir=/usr/share/info' 'build_alias=x86_64-alpine-linux-musl' 'host_alias=x86_64-alpine-linux-musl' 'CC=gcc' 'CFLAGS=-Os -fomit-frame-pointer -D_GNU_SOURCE' 'LDFLAGS=-Wl,--as-needed' 'CPPFLAGS=-Os -fomit-frame-pointer'
20-Mar-2017 15:00:53.955 ----------------------------------------------------
20-Mar-2017 15:00:53.955 BIND 9 is maintained by Internet Systems Consortium,
20-Mar-2017 15:00:53.955 Inc. (ISC), a non-profit 501(c)(3) public-benefit
20-Mar-2017 15:00:53.955 corporation.  Support and training for BIND 9 are
20-Mar-2017 15:00:53.955 available at https://www.isc.org/support
20-Mar-2017 15:00:53.955 ----------------------------------------------------
20-Mar-2017 15:00:53.955 found 1 CPU, using 1 worker thread
20-Mar-2017 15:00:53.955 using 1 UDP listener per interface
.
.
.

あるいは、以下のようにしてコンテナ内のシェルに入って調査します。ps や ls をしているのはただの例なので、実際は自由にトラブルシュートしてください。

kuro@kuro-ph01 [ ~ ]$ docker exec -it bind /bin/ash
/ # ps -ef
PID   USER     TIME   COMMAND
    1 named      0:00 /usr/sbin/named -c /etc/bind/named.conf -u named -g
    8 root       0:00 /bin/ash
   12 root       0:00 ps -ef
/ # ls -l /etc/bind/
total 8
-rw-r--r--    1 1000     1000           306 Mar 20 13:57 kuro.local.zone
-rw-r--r--    1 1000     1000           495 Mar 20 14:04 named.conf

なお、Dockerfile で記述した通り、今回は CMD 行で named を -g で起動させています。このため、exec サブコマンドでなく attach サブコマンドを利用しても、named につながるだけで何のトラブルシュートもできない点には注意が必要です。上記のように、exec サブコマンドを利用して明示的に新規プロセスとしてシェルを起動する必要があります。また、Alpine Linux のシェルは /bin/bash ではなく /bin/ash です。

exec サブコマンドは起動中のコンテナに対してしかできない操作なので、もしそもそもコンテナの起動がコケている場合は、上記のコマンドではシェルに入れません。この場合は、exec サブコマンドを run サブコマンドに置き換えて、明示的に起動させます。これによりコンテナの CMD 指定が /bin/ash でオーバライドされるので、named は起動されずに(=コンテナが落ちずに)シェルがフォアグラウンドで実行されます。

よくあるのは、docker ps したときの STATUS が Restarting のままになるトラブルです。大体の場合、named が設定ファイルの読み込みでコケてこうなります。コケて named が落ちてコンテナが終了するものの、restart オプションによって延々と再度の起動が試行されている状態です。この場合は、ファイルのパーミッションや、ボリュームマウントの指定が正しいか確認します。

Docker Compose で操作する

ここまでで当初の最低限の要件は満たせたので、あとはオマケです。

まっさらな状態にするために、動かしていたコンテナを停止し削除、イメージも全部消しておきます。

kuro@kuro-ph01 [ ~ ]$ docker stop bind
bind
kuro@kuro-ph01 [ ~ ]$ docker rm bind
bind
kuro@kuro-ph01 [ ~ ]$ docker rmi bind
Untagged: bind:latest
Deleted: sha256:bf1581067386a7eb1de3b86773c2d89a4f901ab190d6e74679c541a91e5c1d12
Deleted: sha256:f08f46db294799dbdf60b982abc0431ffb11bb046558888b434ba3cbbe47091e
Deleted: sha256:29500538ea168fc7cee66a294b3ed90a3420cc299ac9d84896b2784ecf81f609
Deleted: sha256:c193e93d56d7b0dc758de256cf867bc724aa3ef55ddb14dfdb804b09303fb84e
kuro@kuro-ph01 [ ~ ]$ docker rmi alpine
Untagged: alpine:latest
Untagged: alpine@sha256:58e1a1bb75db1b5a24a462dd5e2915277ea06438c3f105138f97eb53149673c4
Deleted: sha256:4a415e3663882fbc554ee830889c68a33b3585503892cc718a4698e91ef2a526
Deleted: sha256:23b9c7b43573dd164619ad59e9d51eda4095926729f59d5f22803bcbe9ab24c2
kuro@kuro-ph01 [ ~ ]$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
kuro@kuro-ph01 [ ~ ]$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

あとは、以下な感じになるようにディレクトリを切って、これまでに作ったファイルを移動させておきます。

[~/docker]
 └ [bind]
   ├ Dockerfile
   └ [conf]
     ├ kuro.local.zone
     └ named.conf
kuro@kuro-ph01 [ ~ ]$ mkdir -p docker/bind
kuro@kuro-ph01 [ ~ ]$ mv Dockerfile conf docker/bind

では、今回作ったコンテナを、Docker Compose で操作できるようにしてみます。

本来は複数コンテナを簡単に連携させるために使うものですが、旨味は少ないながらも単一のコンテナを操作したいだけの目的でも使えます。

まずは Docker Compose をインストールします。上記ドキュメントに記載があるコマンドをそのまま実行するだけです。

kuro@kuro-ph01 [ ~ ]$ sudo curl -L "https://github.com/docker/compose/releases/download/1.11.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   600    0   600    0     0    330      0 --:--:--  0:00:01 --:--:--   330
100 8066k  100 8066k    0     0  23737      0  0:05:47  0:05:47 --:--:-- 15090
kuro@kuro-ph01 [ ~ ]$ sudo chmod +x /usr/local/bin/docker-compose
kuro@kuro-ph01 [ ~ ]$ sudo docker-compose --version
docker-compose version 1.11.2, build dfed245

インストールができたら、設定ファイルを作ります。

kuro@kuro-ph01 [ ~ ]$ vim docker/docker-compose.yml
kuro@kuro-ph01 [ ~ ]$ cat docker/docker-compose.yml
version: "2.0"
services:
  bind:
    container_name: bind
    build: bind
    image: bind
    volumes:
      - ./bind/conf:/etc/bind
    ports:
      - 53:53/udp
    restart: always

あとは起動させるだけです。バックグラウンドで実行させたいので、up サブコマンドに -d を付けます。

kuro@kuro-ph01 [ ~ ]$ cd docker
kuro@kuro-ph01 [ ~/docker ]$ sudo docker-compose up -d
Building bind
Step 1 : FROM alpine:latest
latest: Pulling from library/alpine
627beaf3eaaf: Pull complete
Digest: sha256:58e1a1bb75db1b5a24a462dd5e2915277ea06438c3f105138f97eb53149673c4
Status: Downloaded newer image for alpine:latest
 ---> 4a415e366388
Step 2 : RUN apk add bind --no-cache
 ---> Running in 7cb9b9b1d9c5
fetch http://dl-cdn.alpinelinux.org/alpine/v3.5/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.5/community/x86_64/APKINDEX.tar.gz
(1/5) Installing libgcc (6.2.1-r1)
(2/5) Installing libxml2 (2.9.4-r1)
(3/5) Installing bind-libs (9.10.4_p6-r0)
(4/5) Installing libcap (2.25-r1)
(5/5) Installing bind (9.10.4_p6-r0)
Executing bind-9.10.4_p6-r0.pre-install
Executing busybox-1.25.1-r0.trigger
OK: 9 MiB in 16 packages
 ---> 3f4617f7e3e7
Removing intermediate container 7cb9b9b1d9c5
Step 3 : EXPOSE 53/udp
 ---> Running in 828a66a3c5f8
 ---> 47c21a9ae08f
Removing intermediate container 828a66a3c5f8
Step 4 : CMD /usr/sbin/named -c /etc/bind/named.conf -u named -g
 ---> Running in 13e41b8d96c7
 ---> 85edc496264d
Removing intermediate container 13e41b8d96c7
Successfully built 85edc496264d
WARNING: Image for service bind was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating bind

docker-compose.yml の記述に従って、各サービス(コンテナ)が起動されます。build が指定されていた場合は、Dockerfile に従ってコンテナイメージのビルドも行います。

起動状態は、ps サブコマンドで確認できます。

kuro@kuro-ph01 [ ~/docker ]$ sudo docker-compose ps
Name              Command               State         Ports
------------------------------------------------------------------
bind   /usr/sbin/named -c /etc/bi ...   Up      0.0.0.0:53->53/udp

ログの確認は logs サブコマンドです。

kuro@kuro-ph01 [ ~/docker ]$ sudo docker-compose logs
Attaching to bind
bind    | 20-Mar-2017 16:20:24.015 starting BIND 9.10.4-P6 <id:a6837d0> -c /etc/bind/named.conf -u named -g
bind    | 20-Mar-2017 16:20:24.015 running on Linux x86_64 4.4.41-1.ph1-esx #1-photon SMP Tue Jan 10 23:46:44 UTC 2017
bind    | 20-Mar-2017 16:20:24.015 built with '--build=x86_64-alpine-linux-musl' '--host=x86_64-alpine-linux-musl' '--prefix=/usr' '--sysconfdir=/etc/bind' '--localstatedir=/var' '--with-openssl=/usr' '--enable-linux-caps' '--with-libxml2' '--enable-threads' '--enable-filter-aaaa' '--enable-ipv6' '--enable-shared' '--enable-static' '--with-libtool' '--with-randomdev=/dev/random' '--mandir=/usr/share/man' '--infodir=/usr/share/info' 'build_alias=x86_64-alpine-linux-musl' 'host_alias=x86_64-alpine-linux-musl' 'CC=gcc' 'CFLAGS=-Os -fomit-frame-pointer -D_GNU_SOURCE' 'LDFLAGS=-Wl,--as-needed' 'CPPFLAGS=-Os -fomit-frame-pointer'
bind    | 20-Mar-2017 16:20:24.015 ----------------------------------------------------
bind    | 20-Mar-2017 16:20:24.015 BIND 9 is maintained by Internet Systems Consortium,
bind    | 20-Mar-2017 16:20:24.015 Inc. (ISC), a non-profit 501(c)(3) public-benefit
bind    | 20-Mar-2017 16:20:24.015 corporation.  Support and training for BIND 9 are
bind    | 20-Mar-2017 16:20:24.015 available at https://www.isc.org/support
bind    | 20-Mar-2017 16:20:24.015 ----------------------------------------------------
bind    | 20-Mar-2017 16:20:24.015 found 1 CPU, using 1 worker thread
bind    | 20-Mar-2017 16:20:24.015 using 1 UDP listener per interface
.
.
.

コンテナの停止や削除は、それぞれ stop サブコマンドや rm サブコマンドで実行できます。

kuro@kuro-ph01 [ ~/docker ]$ sudo docker-compose stop
Stopping bind ... done
kuro@kuro-ph01 [ ~/docker ]$ sudo docker-compose rm
Going to remove bind
Are you sure? [yN] y
Removing bind ... done
kuro@kuro-ph01 [ ~/docker ]$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

当然ながら、それぞれネイティブの docker コマンドによる操作も可能です。

イメージの削除は docker-compose コマンドではできないので、docker コマンドで個別に行う必要があります。ただし、イメージをビルドしなおしたいだけであれば、build サブコマンドを利用するか、up サブコマンドの実行時に –build オプションを付与することで実現できます。

まとめ

Docker 上で bind を動かし、Docker Compose で操作できるようにしました。

自前でがんばりたくない場合は、探せば Docker Hub に既成の bind サービス用コンテナがいくつかある(例えばこれとか)ので、そういうのを使うのも有効です。その手の既成コンテナはだいたい Webmin がくっついていて非常に楽です。

また、今回は Docker Compose を使いましたが、単一コンテナを扱いたいだけなのであれば、Makefile を利用することもできそうです。Photon OS にも make はインストールできます。

次回は vCSA 6.5 の導入を行います。


Photon OS をデプロイして Docker を動かす

Photon OS を使いたい

先のエントリ で以下のように書きました。

とりあえず vSphere 6.5 の環境をざっと触ってみたいので、ひとまず vCSA を立てていろいろ様子をみて、あとは素直に小さな仮想化基盤としておもちゃにします。

vCSA をお行儀よく(きちんと vCenter Server に FQDN を持たせる形で)立てたい場合は、DNS サーバがないとそもそもインストールができません。

今回はせっかくなのでお行儀のよい構成にしたいのですが、DNS サーバが欲しいからといって適当に CentOS に bind を入れるというのは自分にとって何も新しいことがなくてつまらないので、どうせなら触ったことがないものを触りましょう。

というわけで、引き続き VMware つながりで、今回は Photon OS を使ってみます。Photon OS は、VMware が公開している、コンテナホスト用の軽量 Linux ディストリビューションです。

同じ分野だと CoreOS とか CentOS Atomic Host とか、Snappy Ubuntu Core とか、そのあたりが有名です。今回のこの Photon OS は、VMware プラットフォーム上での動作に最適化されているとのことで、この点がおそらく類似のコンテナホスト用 OS との差別化要素になるのかと思われます。最近、VMware vSphere Integrated Containers という製品も GA になりましたし、VMware がこの分野に注力しているのは確かなようですね。

ちなみに Photon OS は、コンテナホスト用ではあるものの、実は vCenter Server Appliance 6.5(vCSA 6.5)のベース OS としても採用されています。vCSA 6.0 までは、Photon OS ではなく SUSE Linux Enterprise Server でした。

さて、今回の目的は DNS サーバを立てることなので、Photon OS 上に bind を動かすための Docker コンテナを作ることを目指します。本エントリではひとまず、Photon OS をデプロイして、そこで何らかの Docker コンテナを動かす、というところまで進めます。

  • OVA テンプレートのダウンロードとデプロイ
  • 初回起動とパスワードの変更
  • キー配列の変更
  • IP アドレスと DNS の設定
  • ホスト名の変更
  • タイムゾーンと時刻同期の設定
  • ロケールの変更
  • 一般ユーザの追加
  • Docker を動かす

以下、当然ですが執筆時点の情報なので、時間経過に伴って陳腐化する可能性があります。

リソース

公式の Wiki が比較的充実しているので、そこを見ながら作業します。

日本語のリソースでは、gowatana さんの VMTN のウェブログがおそらくいちばん詳しそうです。ただし、こちらは Photon OS をフルインストールした前提で記述されているので、ミニマルの構成では手順が不足する部分がある点は注意が必要です。

今回は、ISO イメージからのインストールではなく、OVA テンプレートを利用して、ミニマル構成での構築を行います。

OVA テンプレートのダウンロードとデプロイ

ダウンロードページから OVA ファイルをダウンロードします。OVA テンプレートは、ミニマル構成のみの提供です。

今回は、手元の環境が vSphere 6.5 なので、仮想ハードウェアバージョンが 11 のものを選びます。

ダウンロード後、vSphere 環境にインポートします。この部分は特に書くほどの手順でもないですが、Wiki にもガイドがあります。

記載通り、パワーオンする前の仮想マシンをテンプレートに変換して保存しておくと、今後インスタンスを増やしたくなったときに便利かもしれません。

At this point, you’ve got a Photon OS instance ready to go; but before you power on that Photon OS instance, consider first converting this VM into a template. By converting this imported VM to a template, you have a master Photon OS instance that can be combined with vSphere Guest Customization to enable rapid provisioning of Photon OS instances.

以降、文字ばかりになるので、せめてかわいいねこの写真でも載せておきます。

初回起動とパスワードの変更

ネットワークアダプタを適切なポートグループに接続させることで、パワーオン後、IP アドレスが DHCP サーバから取得されます。あらかじめ open-vm-tools が導入されているので、付与された IP アドレスは vSphere Client から確認できます。root ユーザでの SSH 接続もデフォルトで許可されています。

よって、起動後、ただちに SSH による接続が可能です。任意の SSH クライアントから root ユーザでログインして初期構築を進めます。初期パスワードは changeme です。

初回ログイン後、root ユーザのパスワードの変更が求められるので、指示に従います。

(current) UNIX password:
New password:
Retype new password:

キー配列の変更

SSH で接続しての操作では通常は意識する必要はありませんが、トラブル時など、どうしても仮想マシンのコンソールで直接操作しなければならない状態に陥ることがあります。これに備えて、キー配列を日本語キーボードに合わせて変更しておきます。

実際の設定方法は上記が参考になりますが、ミニマル構成の場合、そもそもキー配列のライブラリが入っていません。

root@photon-machine [ ~ ]# localectl list-keymaps
Couldn't find any console keymaps.

よって、キー配列のパッケージのインストールから始めます。

root@photon-machine [ ~ ]# tdnf install kbd

Installing:
kbd                             x86_64      2.0.3-2.ph1           3.30 M

Total installed size: 3.30 M
Is this ok [y/N]:y

Downloading:
kbd                                    1599403    100%
Testing transaction
Running transaction

Complete!

tdnf は、Photon OS のパッケージマネージャで、yum の後継である dnf の簡易版(Tiny DNF)です。/etc/yum.repos.d/ 配下の設定ファイルを読むとわかりますが、Photon OS はデフォルトで https://dl.bintray.com/vmware/ 以下のリポジトリを参照するように構成されています。

さて、これで日本語キーボードの情報が使えるようになったので、あとは実際に割り当てて完了です。

root@photon-machine [ ~ ]# localectl list-keymaps | grep jp106
jp106
root@photon-machine [ ~ ]# localectl set-keymap jp106
root@photon-machine [ ~ ]# localectl
   System Locale: LANG=en_US.UTF-8
       VC Keymap: jp106
      X11 Layout: jp
       X11 Model: jp106
     X11 Options: terminate:ctrl_alt_bksp

反映は即時です。この変更は恒久的に保持されます。

IP アドレスと DNS の設定

続いて、DHCP による動的取得ではなく、静的 IP アドレスを利用するように構成します。

構成するインタフェイスは eth0 です。

root@photon-machine [ ~ ]# networkctl
IDX LINK             TYPE               OPERATIONAL SETUP
  1 lo               loopback           carrier     unmanaged
  2 eth0             ether              routable    configured

2 links listed.

基本的にドキュメント通り進めればよいです。systemd を利用した一般的な設定手順と同じです。

まずは静的 IP アドレスを eth0 に付与するための設定ファイルを新しく作ります。設定ミスに備え、この段階ではまだ DHCP を明示的に有効にしておきます。

root@photon-machine [ ~ ]# vim /etc/systemd/network/10-static-eth0.network
root@photon-machine [ ~ ]# cat /etc/systemd/network/10-static-eth0.network
[Match]
Name=eth0

[Network]
DHCP=yes
Address=192.168.0.249/24
Gateway=192.168.0.1
DNS=192.168.0.1
root@photon-machine [ ~ ]# chmod 644 /etc/systemd/network/10-static-eth0.network

systemd.network は名前順に設定ファイルを読み込みますが、最初に [Match] の条件に一致した設定ファイルの内容以外は無視します。

このため、先に DHCP 設定が読まれてしまうと、静的 IP アドレスが付与されないことになってしまいます。このような事態を避けるため、もともと存在していた DHCP 設定用のファイルは、eth0 の設定のあとに読まれるよう、名前を変える必要があります。将来的にネットワークアダプタを追加した際の動きを考慮しないのであれば、そもそもファイル自体を消してしまっても構いません。

root@photon-machine [ ~ ]# mv /etc/systemd/network/10-dhcp-en.network /etc/systemd/network/20-dhcp-en.network

この後、ネットワークサービスを再起動します。

root@photon-machine [ ~ ]# systemctl restart systemd-networkd

10-static-eth0.network に DHCP=yes を記載したことで、ここでネットワークサービスを再起動しても、もともと DHCP で付与されていた IP アドレスが保持されます(正確には DHCP サーバから同じ IP アドレスが再度割り当てられているだけです)。SSH のセッションが切断されることなく、操作を続行できます。

以下のコマンドで、IP アドレスが 2 つ付与されていることが確認できます。

root@photon-machine [ ~ ]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:50:56:87:dd:02 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.249/24 brd 192.168.0.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet 192.168.0.11/24 brd 192.168.0.255 scope global secondary dynamic eth0
       valid_lft 86334sec preferred_lft 86334sec
    inet6 fe80::250:56ff:fe87:dd02/64 scope link
       valid_lft forever preferred_lft forever

上記のように正常に静的 IP アドレスが付与されていることが確認できたら、新しい IP アドレスに SSH で接続しなおします。

その後、改めて 10-static-eth0.network を編集し、DHCP を無効化したあと、再度ネットワークサービスを再起動し、IP アドレスが一つだけになったことを確認します。

root@photon-machine [ ~ ]# vim /etc/systemd/network/10-static-eth0.network
root@photon-machine [ ~ ]# cat /etc/systemd/network/10-static-eth0.network
[Match]
Name=eth0

[Network]
DHCP=no
Address=192.168.0.249/24
Gateway=192.168.0.1
DNS=192.168.0.1
root@photon-machine [ ~ ]# systemctl restart systemd-networkd
root@photon-machine [ ~ ]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:50:56:87:dd:02 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.249/24 brd 192.168.0.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::250:56ff:fe87:dd02/64 scope link
       valid_lft forever preferred_lft forever

これで、安全に DHCP による動的取得から静的 IP アドレスの利用へ切り替えられました。

ホスト名の変更

OVA テンプレートからデプロイした場合、ホスト名はデフォルトで photon-machine になっています。

ホスト名は hostnamectl コマンドで変えられます。特に目新しいことはありません。

root@photon-machine [ ~ ]# hostnamectl set-hostname kuro-ph01.kuro.local
root@photon-machine [ ~ ]# hostnamectl
   Static hostname: kuro-ph01.kuro.local
         Icon name: computer-vm
           Chassis: vm
        Machine ID: 889b661d4b9446c48002b7ed596a00a4
           Boot ID: 67367e5ce4094074be76a82f0a3c3a7b
    Virtualization: vmware
  Operating System: VMware Photon/Linux
            Kernel: Linux 4.4.41-1.ph1-esx
      Architecture: x86-64

反映は即時です。この変更は恒久的に保持されます。

タイムゾーンと時刻同期の設定

以下のエントリによると、フルインストール構成ではいろいろと設定が必要そうです。

が、OVA からデプロイした場合は、デフォルトで時刻同期が動いていました。Google の NTP サーバと同期しているようです。

root@photon-machine [ ~ ]# timedatectl
      Local time: Sun 2017-03-19 10:05:43 UTC
  Universal time: Sun 2017-03-19 10:05:43 UTC
        RTC time: Sun 2017-03-19 10:05:43
       Time zone: UTC (UTC, +0000)
 Network time on: yes
NTP synchronized: yes
 RTC in local TZ: no
root@photon-machine [ ~ ]# systemctl status systemd-timesyncd -l
● systemd-timesyncd.service - Network Time Synchronization
   Loaded: loaded (/usr/lib/systemd/system/systemd-timesyncd.service; enabled; vendor preset: enabled)
   Active: active (running) since Sun 2017-03-19 06:22:48 UTC; 3h 42min ago
     Docs: man:systemd-timesyncd.service(8)
 Main PID: 128 (systemd-timesyn)
   Status: "Synchronized to time server 216.239.35.12:123 (time4.google.com)."
    Tasks: 2
   CGroup: /system.slice/systemd-timesyncd.service
           `-128 /lib/systemd/systemd-timesyncd

Mar 19 06:52:29 photon-machine systemd-timesyncd[128]: Synchronized to time server 216.239.35.8:123 (time3.google.com).
Mar 19 09:37:34 photon-machine systemd-timesyncd[128]: Network configuration changed, trying to establish connection.
Mar 19 09:37:34 photon-machine systemd-timesyncd[128]: Synchronized to time server 216.239.35.8:123 (time3.google.com).
Mar 19 09:40:00 photon-machine systemd-timesyncd[128]: Network configuration changed, trying to establish connection.
Mar 19 09:40:10 photon-machine systemd-timesyncd[128]: Timed out waiting for reply from 216.239.35.8:123 (time3.google.com).
Mar 19 09:40:10 photon-machine systemd-timesyncd[128]: Synchronized to time server 216.239.35.12:123 (time4.google.com).
Mar 19 09:40:12 photon-machine systemd-timesyncd[128]: Network configuration changed, trying to establish connection.
Mar 19 09:40:12 photon-machine systemd-timesyncd[128]: Synchronized to time server 216.239.35.12:123 (time4.google.com).
Mar 19 09:43:32 photon-machine systemd-timesyncd[128]: Network configuration changed, trying to establish connection.
Mar 19 09:43:32 photon-machine systemd-timesyncd[128]: Synchronized to time server 216.239.35.12:123 (time4.google.com).

ConditionVirtualization は !container になっていました。コンテナ環境内でなければ勝手に動くようです。

root@photon-machine [ ~ ]# grep ConditionVirtualization /lib/systemd/system/systemd-timesyncd.service
ConditionVirtualization=!container

というわけで、何もせず、タイムゾーンだけ変えます。

タイムゾーンはさすがにミニマル構成といえど入っていました。反映させるだけで完了です。

root@photon-machine [ ~ ]# timedatectl list-timezones | grep Asia/Tokyo
Asia/Tokyo
root@photon-machine [ ~ ]# timedatectl set-timezone Asia/Tokyo
root@photon-machine [ ~ ]# timedatectl
      Local time: Sun 2017-03-19 19:11:09 JST
  Universal time: Sun 2017-03-19 10:11:09 UTC
        RTC time: Sun 2017-03-19 10:11:09
       Time zone: Asia/Tokyo (JST, +0900)
 Network time on: yes
NTP synchronized: yes
 RTC in local TZ: no
root@photon-machine [ ~ ]# date
Sun Mar 19 19:11:32 JST 2017

ロケールの変更

ロケールはデフォルトで en_US.UTF-8 になっています。このままでもよいのですが、せっかくなので ja_JP.UTF-8 に変更します。

が、最初は何のロケール情報もありません。

root@photon-machine [ ~ ]# localectl list-locales

必要なロケール情報を自分で生成する必要があります。

で、上記ページには /usr/share/locale/locale.alias から言語を選んで /etc/locale-gen.conf に追記して locale-gen.sh を叩けと書いてありますが、実際はそもそも /usr/share/locale/locale.alias が存在しないですし、そのまま進めても怒られてしまいます。

root@photon-machine [ ~ ]# locale-gen.sh
Generating locales...
en_US.ISO-8859-1...locale alias file `/usr/share/locale/locale.alias' not found: No such file or directory

というわけで、強引ですが空ファイルでよいので /usr/share/locale/locale.alias を作ってやると回避できます。これ、おそらく将来のリリースで修正されるかと。

最終的には以下の手順です。言語は好きに読み替えてください。

root@photon-machine [ ~ ]# touch /usr/share/locale/locale.alias
root@photon-machine [ ~ ]# echo -e "ja_JP\t\tUTF-8" >> /etc/locale-gen.conf
root@photon-machine [ ~ ]# echo -e "ja_JP.UTF-8\tUTF-8" >> /etc/locale-gen.conf
root@photon-machine [ ~ ]# locale-gen.sh
Generating locales...
en_US.ISO-8859-1... done
en_US.UTF-8... done
ja_JP.UTF-8... done
ja_JP.UTF-8... done
Generation complete.
root@photon-machine [ ~ ]# localectl list-locales
en_US
en_US.iso88591
en_US.utf8
ja_JP
ja_JP.utf8
root@photon-machine [ ~ ]# localectl set-locale LANG="ja_JP.UTF-8"
root@photon-machine [ ~ ]# localectl
   System Locale: LANG=ja_JP.UTF-8
       VC Keymap: jp106
      X11 Layout: jp
       X11 Model: jp106
     X11 Options: terminate:ctrl_alt_bksp

反映は即時です。恒久的に保持されます。

一般ユーザの追加

いつまでも root なのはアレなので、一般ユーザを作ります。このユーザには Docker 操作用の特権を与えたいので、明示的に docker グループを作成して所属させます。sudo ができるように wheel にも所属させます。本当は一つのユーザに複数の役割は持たせるべきではないですが、よしとします。

root@photon-machine [ ~ ]# groupadd docker
root@photon-machine [ ~ ]# useradd -m -g docker -G wheel kuro
root@photon-machine [ ~ ]# passwd kuro
New password:
Retype new password:
passwd: password updated successfully

そして実はデフォルトでは sudo コマンドがないのでインストールします。

root@photon-machine [ ~ ]# tdnf install sudo

Installing:
sudo                            x86_64      1.8.15-3.ph1          3.47 M

Total installed size: 3.47 M
Is this ok [y/N]:y

Downloading:
sudo                                   1319187    100%
Testing transaction
Running transaction
groupadd: group 'wheel' already exists

Complete!

kuro ユーザで SSH で接続して、問題なければ root での直接ログインを禁止させます。PermitRootLogin を no にするだけです。OVA テンプレートからデプロイした場合は、デフォルトで yes になっています。

ここ以降、kuro ユーザで操作しています。

kuro@kuro-ph01 [ ~ ]$ sudo vim /etc/ssh/sshd_config
kuro@kuro-ph01 [ ~ ]$ sudo tail /etc/ssh/sshd_config
Subsystem       sftp    /usr/libexec/sftp-server

# Example of overriding settings on a per-user basis
#Match User anoncvs
#       X11Forwarding no
#       AllowTcpForwarding no
#       PermitTTY no
#       ForceCommand cvs server
PermitRootLogin no
UsePAM yes
kuro@kuro-ph01 [ ~ ]$ sudo systemctl restart sshd
kuro@kuro-ph01 [ ~ ]$ sudo systemctl status sshd
● sshd.service - OpenSSH Daemon
   Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)
   Active: active (running) since Sun 2017-03-19 19:52:39 JST; 9s ago
 Main PID: 1169 (sshd)
    Tasks: 1
   CGroup: /system.slice/sshd.service
           `-1169 /usr/sbin/sshd -D

Mar 19 19:52:39 kuro-ph01.kuro.local systemd[1]: Started OpenSSH Daemon.
Mar 19 19:52:39 kuro-ph01.kuro.local sshd[1169]: Server listening on 0.0.0.0....
Mar 19 19:52:39 kuro-ph01.kuro.local sshd[1169]: Server listening on :: port 22.
Hint: Some lines were ellipsized, use -l to show in full.

Docker を動かす

さて、ようやく本題です。が、もはやここまで来れば以降は Docker のごく一般的な操作の話なだけで特別なことはありません。

Docker 自体はデフォルトでインストールされています。

kuro@kuro-ph01 [ ~ ]$ docker -v
Docker version 1.12.1, build 23cf638

自動起動は無効の状態です。起動させて、自動起動も有効にします。

kuro@kuro-ph01 [ ~ ]$ sudo systemctl start docker
kuro@kuro-ph01 [ ~ ]$ sudo systemctl status docker
● docker.service - Docker Daemon
   Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: enabled)
   Active: active (running) since Sun 2017-03-19 19:27:00 JST; 5s ago
     Docs: http://docs.docker.com
 Main PID: 618 (dockerd)
    Tasks: 6
   CGroup: /system.slice/docker.service
           `-618 dockerd --containerd /run/containerd.sock

Mar 19 19:27:00 kuro-ph01.kuro.local docker[618]: time="2017-03-19T19:27:00.221740890+09:00" level=warnin...ght"
Mar 19 19:27:00 kuro-ph01.kuro.local docker[618]: time="2017-03-19T19:27:00.221854117+09:00" level=warnin...ice"
Mar 19 19:27:00 kuro-ph01.kuro.local docker[618]: time="2017-03-19T19:27:00.222276327+09:00" level=info m...rt."
Mar 19 19:27:00 kuro-ph01.kuro.local docker[618]: time="2017-03-19T19:27:00.238793455+09:00" level=info m...lse"
Mar 19 19:27:00 kuro-ph01.kuro.local docker[618]: time="2017-03-19T19:27:00.297271226+09:00" level=info m...ess"
Mar 19 19:27:00 kuro-ph01.kuro.local docker[618]: time="2017-03-19T19:27:00.327413635+09:00" level=info m...ne."
Mar 19 19:27:00 kuro-ph01.kuro.local docker[618]: time="2017-03-19T19:27:00.327488574+09:00" level=info m...ion"
Mar 19 19:27:00 kuro-ph01.kuro.local docker[618]: time="2017-03-19T19:27:00.327504134+09:00" level=info m...12.1
Mar 19 19:27:00 kuro-ph01.kuro.local systemd[1]: Started Docker Daemon.
Mar 19 19:27:00 kuro-ph01.kuro.local docker[618]: time="2017-03-19T19:27:00.333295270+09:00" level=info m...ock"
Hint: Some lines were ellipsized, use -l to show in full.
kuro@kuro-ph01 [ ~ ]$ systemctl enable docker
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.

起動しました。適当なコンテナイメージを動かしてみます。適当に vmwarecna/nginx など。

kuro@kuro-ph01 [ ~ ]$ docker run -d -p 80:80 vmwarecna/nginx
Unable to find image 'vmwarecna/nginx:latest' locally
latest: Pulling from vmwarecna/nginx
a3ed95caeb02: Pull complete
b6f2388a20dd: Pull complete
a305e4b888ce: Pull complete
80596a504ef3: Pull complete
99c028eff2a4: Pull complete
a1cee46bc434: Pull complete
9bd9868012b9: Pull complete
6fa7100a2613: Pull complete
Digest: sha256:f73bbae0f31823c06478b1fa5efb4957bc25239802fd5ea94e4442c0a6090d23
Status: Downloaded newer image for vmwarecna/nginx:latest
02e4fcf9b805a01b0d7bc203e2b78f655fa9aed2a7ff5a6abfbc93703b13a41e

勝手にリポジトリ(Docker Hub)からコンテナイメージを探してダウンロードして起動してくれました。簡単ですね。

ダウンロードしたコンテナイメージと起動状態は以下のように確認できます。

kuro@kuro-ph01 [ ~ ]$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
vmwarecna/nginx     latest              c237cfda7789        23 months ago       93.48 MB
kuro@kuro-ph01 [ ~ ]$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                         NAMES
02e4fcf9b805        vmwarecna/nginx     "nginx -g 'daemon off"   2 minutes ago       Up 2 minutes        0.0.0.0:80->80/tcp, 443/tcp   hungry_stonebraker

ブラウザで http://<IP アドレス>/ にアクセスして、以下のようにデモンストレーションのページが開けば成功です。

動くのが分かったので、お掃除します。まずはコンテナの停止。

kuro@kuro-ph01 [ ~ ]$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                         NAMES
02e4fcf9b805        vmwarecna/nginx     "nginx -g 'daemon off"   2 minutes ago       Up 2 minutes        0.0.0.0:80->80/tcp, 443/tcp   hungry_stonebraker
kuro@kuro-ph01 [ ~ ]$ docker stop 02e4fcf9b805
02e4fcf9b805
kuro@kuro-ph01 [ ~ ]$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
02e4fcf9b805        vmwarecna/nginx     "nginx -g 'daemon off"   5 minutes ago       Exited (0) 8 seconds ago                       hungry_stonebraker

止めたらコンテナを消します。

kuro@kuro-ph01 [ ~ ]$ docker rm 02e4fcf9b805
02e4fcf9b805
kuro@kuro-ph01 [ ~ ]$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

イメージも要らないので消します。

kuro@kuro-ph01 [ ~ ]$ docker rmi c237cfda7789
Untagged: vmwarecna/nginx:latest
Untagged: vmwarecna/nginx@sha256:f73bbae0f31823c06478b1fa5efb4957bc25239802fd5ea94e4442c0a6090d23
Deleted: sha256:c237cfda7789b89aaed30c646fb885e89e423b4adc708f3a02981f6c4d55aed3
Deleted: sha256:7df42bfffcdf0ef09a8a7e89df5a7c3e35d10bfa2eb39fb1a367b6fdad79f858
Deleted: sha256:4c2eef2ffbe175f3372463bf3a5223ca9a524360510f30814988a620ebfb0248
Deleted: sha256:db57fe254f4599d7aa2a949a608cb41d31a828aa15e6bd9c57e6e4785e10d224
Deleted: sha256:5dab3f8810166fa8a16f74aa4e71b8d658ad6e6ebfe6851509df386d55b02ac0
Deleted: sha256:e327d626ee3d33195a1efe565a9d98415b4ef4ddfcfa5757609f86a661c13ca6
Deleted: sha256:7d433cbce64586e009ebffefa71b27573a374f3b2e6d66252a96cea88d7ccd31
Deleted: sha256:992d02669b3c389cece05fee1714f81bd9694ba23aeedff75cd054611a4b5d4c
Deleted: sha256:58647153590d6ca2ecc30e0ddb208b9b24743967ce925383df462ec710699cc7
Deleted: sha256:fd622e88e74de6d4b74f9e7bf684bac2238f59b929475f70f092beb6db6b61db
Deleted: sha256:f835ef83693c18a2694bedb0ecaca78e968a4846242abae2b704db2ac7140f3f
Deleted: sha256:5062638dae85cb1e1c79154b25b0e2d4518f6267742638256ec619d937f3183e
Deleted: sha256:3911ed2f627c2ff8e453a86182f7f1570ad95595b443507bc3d7eb05f3c53f41
Deleted: sha256:affbb86305e946bf861b7ec77af0e66287eaf8649bb4522d718eeac6079d94b7
Deleted: sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef
kuro@kuro-ph01 [ ~ ]$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

まとめ

Photon OS を OVA テンプレートからデプロイし、必要な初期設定を行ったあと、Docker の動作確認を行いました。

今回はここまで。あとはどうにでもなりますね。次回は Docker 上で bind を動かします