カテゴリー別アーカイブ: Docker

rivarunが404になる時

結論だけ言うと、チャンネルのオートスキャンで治る時がありますよって事なのですが・・・

ちなみに、オートスキャンは次のコマンドで実行できます(参考:公式サイト)。

$ curl -X PUT "http://localhost:40772/api/config/channels/scan"

localhostは適宜変えてください。

これで地上波のみですがチャンネルが自動でスキャンされて設定が保存されます。

経緯

ChinachuのDockerレポジトリからセットアップを試していたのですが、Mirakurunがうまく動いていないようでChinachu側で番組表が取れませんでした。

rivarunを叩くと接続はできるものの404になってしまいます。そこで、

$ mirakurun log

でログを出したところ、次のようなエラー文を発見。

stream has closed before get network

調べてみるとチャンネル設定がうまくいっていない時に出るエラーのようです。

さらに調べると、mirakurunにはチャンネルスキャンという便利機能もあるようなので、

$ curl -X PUT "http://mirakurun-host:40772/api/config/channels/scan"

を実行したところ番組表が取れるようになっていました。

オートスキャン便利ですね。次から自動で実行するようにしておこう・・・

aptでDocker更新時にエラー

自宅サーバーのメンテナンス作業をしていたら、以下のエラーに遭遇しました。

# apt upgrade
(中略)
docker-ce (17.09.0~ce-0~ubuntu) を設定しています ...
Job for docker.service failed because the control process exited with error code. See "systemctl status docker.service" and "journalctl -xe" for details.
invoke-rc.d: initscript docker, action "start" failed.
● docker.service - Docker Application Container Engine
   Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
   Active: activating (auto-restart) (Result: exit-code) since 日 2017-10-15 15:27:25 JST; 31ms ago
     Docs: https://docs.docker.com
  Process: 15537 ExecStart=/usr/bin/dockerd -H fd:// (code=exited, status=1/FAILURE)
 Main PID: 15537 (code=exited, status=1/FAILURE)

10月 15 15:27:25 luka.home systemd[1]: Failed to start Docker Application Container Engine.
10月 15 15:27:25 luka.home systemd[1]: docker.service: Unit entered failed state.
10月 15 15:27:25 luka.home systemd[1]: docker.service: Failed with result 'exit-code'.
dpkg: パッケージ docker-ce の処理中にエラーが発生しました (--configure):
 サブプロセス インストール済みの post-installation スクリプト はエラー終了ステータス 1 を返しました
処理中にエラーが発生しました:

どうやら、Docker起動時にホストを指定する項目が/etc/docker/daemon.jsonと/lib/systemd/system/docker.serviceの両方で指定されていたため、重複してエラーになっていたようです。

私の環境ではswarm modeでクラスタを組んでいるため、エラー文にもある ExecStart=/usr/bin/dockerd -H fd:// の部分は必要ありません。

/lib/systemd/system/docker.service を編集して、

ExecStart=/usr/bin/dockerd -H fd://

ExecStart=/usr/bin/dockerd

に書き換えを行い apt upgrade を実行したところ、エラーは解消されました。

fd:// オプションを保持したい場合は、 /etc/docker/daemon.json に設定を行う形になりそうです。

docker-machineのAzureドライバでdocker daemonが起動しない

これは2017年7月26日時点での問題なので、今後の修正で改善される可能性が高いです。

それまでの暫定的なTIPSとして書いておきます。

docker-machineのAzureドライバでdockerデーモンを作成したのですが、dockerデーモンが起動していない旨のエラーが出てしまいコマンドが完了しませんでした。

まずはログを見てみます。

$ docker-machine ssh machine-name
$ journalctl -u docker.service -o json
(中略)
`docker daemon` is not supported on Linux. Please run `dockerd` directly
~~~

どうやらDockerのアップデートでdocker daemonコマンドがいい感じに使えなくなったようです。

https://github.com/docker/machine/issues/4156#issuecomment-311964422

この記事が参考になりました。

$ sudo sed -ie "s/docker daemon/dockerd/g" /etc/systemd/system/docker.service.d/10-machine.conf
$ sudo systemctl daemon-reload
$ sudo service docker restart

これで無事解決です。とはいえ、なるはやでデフォルト起動してくれるようになってほしいですね・・・

Dockerの公式MySQLイメージでデータベースをスケールアウトする

発端

Dockerにswarm modeが追加されてから、スケールアウトの難易度がいっそう下がったように思います。

ただ、公式のmysqlイメージにはスケールアウトに対応しておらず、データベースの負荷分散は未だに難易度が高いようです。

そこで、なるべく公式のmysqlのイメージを使いながらswarm modeにあった設定を行えたらと思い、調査および構築を行ってみました。

なお、mysqlのスケールアウトを行ったのは今回が初めてですので、きれいな書き方とかは全然できてません。

あとシェルスクリプトもあんまり上手に書けませんので、そのあたりはご容赦ください。。

前提

MySQLにはgaleraやmysql clusterなどの冗長化構成もあるようですが、今回はなるべくmysqlでスケールアウトを行うことを考えます。

そのため、簡単に構築できるマスター、スレーブでのレプリケーションを設定します。

マスター・スレーブのレプリケーションのストラテジは次のようになるようです。

  • マスターとスレーブを用意し、DBの変更はすべてマスターで行う
  • マスターでの変更はすべてレプリケーション専用ユーザーを通してスレーブに転送され、適用される
  • スレーブでの変更は、マスターには反映されない
  • レプリケーション設定後でも新しいユーザーやデータベースを作成でき、それらは正しくスレーブに転送される

今回はマスターが1台、スレーブがスケールアウト可能となる構成を行います。

設定

docker-stack.yml

environmentとvolumesの値は適宜設定してください。

複数ノードの場合はマスターのデータボリュームはnfsで共有するなど、どこからでも参照できるようにしておくといいと思います。

また、設定ファイルはすべてのノードから参照できるようにしておいてください。

version: '3'

services:
  master:
    image: mysql
    environment:
      - MYSQL_ROOT_PASSWORD=
      - MYSQL_DATABASE=
      - MYSQL_USER=repl
      - MYSQL_PASSWORD=
    volumes:
      - /path/to/data:/var/lib/mysql
      - /path/to/set_master.sql:/docker-entrypoint-initdb.d/set_master.sql
    command: mysqld --server-id=101 --log-bin=/var/lib/mysql/mysql-bin.log
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints:
          - node.role == worker

  slave:
    image: mysql
    environment:
      - MYSQL_ROOT_PASSWORD=
      - MYSQL_DATABASE=
      - MYSQL_USER=repl
      - MYSQL_PASSWORD=
    volumes:
      - /path/to/docker-entrypoint.sh:/usr/local/bin/docker-entrypoint.sh
      - /path/to/set_slave.sh:/docker-entrypoint-initdb.d/set_slave.sh
    deploy:
      mode: global
      placement:
        constraints:
          - node.role == worker

追加で必要なファイルは

  • set_master.sql
  • docker-entrypoint.sh(デフォルトのものを上書きします)
  • set_slave.sh

となります。

set_master.sql

GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';

docker-entrypoint.sh

このファイルは、先頭にちょっとだけ追加するだけです。

公式イメージからdocker-entrypoint.shを取ってきて、先頭を以下のように修正します。

#!/bin/bash
set -eo pipefail
shopt -s nullglob

# 〜 ここから追加 〜
# set server_id
IPADDR=`cat /etc/hosts | grep -o '10.0.[0-9]*.[0-9]*' | cut -d . -f 4`
SERVER_ID=`expr 200 + ${IPADDR}`
if [ ! -f /etc/mysql/conf.d/server_id.cnf ]; then
    touch /etc/mysql/conf.d/server_id.cnf
    echo "[mysqld]" > /etc/mysql/conf.d/server_id.cnf
    echo "server-id=${SERVER_ID}" >> /etc/mysql/conf.d/server_id.cnf
    echo "read_only" >> /etc/mysql/conf.d/server_id.cnf
fi
# 〜 追加ここまで 〜

# if command starts with an option, prepend mysqld

set_slave.sh

最初のMYSQL_PWDと最後の方のMASTER_PASSWORDを適切に設定してください。

#!/bin/bash
export MYSQL_PWD=
mysqldump -h master -u root \
--all-databases \
--events \
--single-transaction \
--flush-logs \
--master-data=2 \
--hex-blob \
--default-character-set=utf8 | mysql -u root
LOG=`mysql -s -u root -h master -e "show master status" | tail -n 1 | cut -f 1`
POS=`mysql -s -u root -h master -e "show master status" | tail -n 1 | cut -f 2`
mysql -u root -e "CHANGE MASTER TO MASTER_HOST='master', MASTER_PORT=3306, MASTER_USER='repl', MASTER_PASSWORD='', MASTER_LOG_FILE='${LOG}', MASTER_LOG_POS=${POS};"
mysql -u root -e "start slave;"

こちらには、

$ chmod a+x set_slave.sh

して実行可能にしておいてください。

実行

適切に設定できたら、docker stack deployでデプロイを行います。

docker stack deploy --compose-file ./docker-stack.yml db

確認してみます。

$ docker stack ps db
ID            NAME                                      IMAGE         NODE       DESIRED STATE  CURRENT STATE              ERROR  PORTS
XXXXXXXXXXXX  db_slave.XXXX  mysql:latest  node1  Running        Running about an hour ago
XXXXXXXXXXXX  db_slave.XXXX  mysql:latest  node2  Running        Running about an hour ago
XXXXXXXXXXXX  db_master.1                         mysql:latest  node1  Running        Running about an hour ago

スレーブで SHOW SLAVE STATUS\G などを実行したりマスターにデータを登録したりして、スレーブが適切に動いているかを確認してください。

ポイント

今回最も苦労した点は、各サーバーにserver_idを設定する部分でした。

サーバーIDは、マスターは固定ですが、スレーブは200にIPアドレスの最下位の値を足しています。

この値はコンテナが実行されてから初めて設定可能になるので、volumesでcnfファイルを指定することはできません。

また、composeのcommandディレクティブで環境変数の展開も試しましたが、どうやらコンテナ起動時のスクリプトではcommandで指定した環境変数を展開してくれず、うまく行きませんでした。

そこで、いっそのことコンテナ起動時に実行されるdocker-entrypoint.shに設定ファイルを動的に作成するスクリプトを仕込もうというのが、今回行っている対応策です。

docker-stack.ymlでは、volumesにdocker-compose.shを上書きする記述を行っています。

あとがき

正直このやり方が良いとは到底思えませんし、シェルスクリプトも試行錯誤で書いたので改善の余地ありありですね・・・

改善点やアドバイスなどあればぜひコメントお願い致します。。

docker stackコマンドでのtips

dockerにswarm modeが追加され、KVS無しでもオーバーレイネットワークが使えるようになりました。

ただ、swarm modeでは以前のdocker-composeコマンドでオーバーレイネットワークに接続することはできず、docker stackコマンドを使います。

この辺りは別で記事を書くとして、取り急ぎ運用で困った事とその解決策(妥協策?)を書いていきます。

.env ファイルが使えない

docker-composeやdocekr stackではdocker-compose.ymlファイルにサービスの設定を書きます。

また、docker-composeコマンドはカレントディレクトリの.envファイルを読んで環境変数をセットしてくれました。

しかしdocker stackは現状では残念ながら.envファイルを読んでくれません。

そこで、以下のコマンドで暫定的に対応することにします。

# env $(cat .env | grep ^[A-Z] | xargs) docker stack deploy --compose-file docker-compose.yml stack_name

これで環境変数が展開されるみたいです。

認証が必要なregistryを使う

–with-registry-auth オプションを使うといいようです。

# docker stack deploy --with-registry-auth --compose-file docker-compose.yml stack_name

今はまだ不便なところもあるdocker stackですが、swarm modeは以前よりデプロイが簡単でとてもいいです。

今後のアップデートでどんどん使いやすくなっていくと思いますので、そのあたりはすごく期待が持てそうですね。

自己証明書でDocker registryを構築

発端

以前から自分でビルドしたイメージを保存するためにLets Encryptの証明書を使ったdocker registryを持っていましたが、以下の理由から自己証明書を使ったものに切り替えました。

  • 外部公開する必要がない
  • 自己証明書の方がお手軽に建てられる
  • ドメイン名の縛りが無いので、LAN内で複数建てられる

作業

実際の構築はほとんどこちらの記事を参考にしました。

# mkdir certs auth data
# openssl req -newkey rsa:4096 -nodes -sha256 -keyout certs/domain.key -x509 -days 365 -out certs/domain.crt
# htpasswd -Bbn user password > auth/htpasswd
# vim docker-compose.yml
registry:
  restart: always
  image: registry:2
  ports:
    - 5000:5000
  environment:
    REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt
    REGISTRY_HTTP_TLS_KEY: /certs/domain.key
    REGISTRY_AUTH: htpasswd
    REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
    REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
  volumes:
    - ./data:/var/lib/registry
    - ./certs:/certs
    - ./auth:/auth
# docker-compose up -d

これで構築は完了です。

あとは各ノードに先ほど作成したdomain.crtを配置します。

これについては公式ドキュメントが参考になりました。

domain.crtを対象ノードにコピーして、/etc/docker/certs.d/{ドメイン名:ポート}/ca.crt に配置してください。

これで自己証明書のregistryを警告なしで使えるようになります。

# docker login {ドメイン名:ポート}
(ユーザー名:パスワードを入力)
# docker pull busybox
# docker tag busybox {ドメイン名:ポート}/test
# docker push {ドメイン名:ポート}/test
# docker rmi {ドメイン名:ポート}/test
# docker pull {ドメイン名:ポート}/test

以前までinsecure registryに設定を書いていましたが、こっちの方が全然楽でいいですね。

今後はこちらの方法を使っていきたいと思います。

Ubuntu 16.04でDocker Daemonの設定

発端

先日自宅サーバーの大幅な整備を行ったのですが、OSをUbuntu 14.04からUbuntu 16.04に更新したところDocker Swarmのノードとして認識されなくなってしまいました。

ノードのOSを全てアップグレードする予定だったので、解決策を模索して見ました。

調査

どうやらSwarmマネージャからノードへの通信ができていないようだったので、netstatでポートを見てみると/etc/default/dockerで設定したポートが開いていませんでした。

調べてみるとUbuntu 16.04ではDockerはsystemdから起動されるらしく、systemdは/etc/default/dockerを読んでいないようです。

Ubuntu 14.04ではupstartから起動が行われていましたので、/etc/default/dockerを編集すればよかったのですが、これではダメみたいですね。

この辺りは、公式ドキュメントに詳しく書いてあります。

解決策

そこで、systemdから起動した場合でも読んでくれる設定ファイルを探したところ、/etc/docker/daemon.jsonというファイルを編集すれば良さそうというドキュメントを見つけました。

このファイルはjsonなので、過去のようにDOCKER_OPTS=のように記述することはできません。

書式については、公式ドキュメントが参考になりました。

設定値については、特に調べなくてもスムーズに移行ができました。

なんとなくですが、jsonの方がメンテしやすそうで良いですよね(笑

このファイルを編集した後に、

# systemctl restart docker

とすれば、設定が反映されます。

ただし、再起動時に以下のエラーが出るようです。

unable to configure the Docker daemon with file /etc/docker/daemon.json: the following directives are specified both as a flag and in the configuration file: hosts:

これについては、/lib/systemd/system/docker.serviceの記述を変更することで対応できますが、編集して良いものなのかわからないので、最終的にはsystemdからの起動をやめて、コマンドからデーモンを起動する方が良いんでしょうかね。

この辺りのスレッドでも触れられているので、見てみると良いのかもしれません。

これについては、もう少し調査を行ってみようと思います。

Dockerのネットワークからの通信がufwで弾かれる場合

Dockerコンテナからホストで動いているconsulに通信しようとしたところ、ufwで弾かれていました。

コンテナからホストへの通信は全てdocker0というネットワークインターフェースからホストの物理デバイスへ行われるため、これを許可してやる必要があるようです。

sudo ufw allow in on docker0

これでdocker0からホストへの通信が全て許可されます。

ただし、セキュリティを気にするなら個別に設定する必要がありそうですね。

でもDockerのportsオプションで設定される場合はホスト→コンテナの通信がファイアウォールで弾かれませんよね。

Dockerがiptablesを自動的に変更しているんですかね。今度調べてみよう・・・

DockerでのWordPress永続化

先日雷でサーバーを全部落とさざるを得ない状況になってしまったので、サーバーの再起動後にDockerのコンテナを全て再起動しました。

コンテナを作り直した結果、データベースは永続化していたので投稿は消えなかったのですが、Wordpressのプラグインやテーマ、アップロードした画像などが消えてしまいました。

投稿が少なかったのでよかったですが、画像が永続化されないのは割と困りますので、設定を見直してみました。

今までのdocker-compose.ymlファイルがこんな感じです。

version: '2'
services:
  db:
    image: mysql
    volumes:
      - "./db:/var/lib/mysql"
    expose:
      - "3306"
    environment:
      MYSQL_ROOT_PASSWORD: hogefuga
      MYSQL_DATABASE: hogefuga
      MYSQL_USER: hogefuga
      MYSQL_PASSWORD: hogefuga

  wordpress:
    depends_on:
      - db
    image: wordpress
    ports:
      - "80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_PASSWORD: hogefuga

調べた結果、Wordpressの運用経験が少ないので知りませんでしたが、プラグインなどのコンテンツはwp-contentというディレクトリに入っているみたいですね。

このディレクトリも永続化させるように、Wordpressの設定に2行追加しました。

    volumes:
      - "./wp-content:/var/www/html/wp-content"

これでめでたくWordpressのプラグインやテーマも永続化できるようになりました。

Dockerでservice endpoint with name already existsと出た時

発端

ノードを再起動した後にsacleしようとしたところ、次のようなエラーが出ました。

Error response from daemon: service endpoint with name es already exists

古いコンテナを削除してもこのエラーが出る場合は、docker networkにコンテナの登録が残ってしまっている可能性が高いので、次のコマンドで削除できます。

docker network disconnect -f [ネットワーク名] [コンテナ名]

一応気になったので、実際に登録されているか調べてみました。

$ docker network inspect [network_name] | grep -n [container_name]
154                 "Name": "[container_name]",
$ docker network inspect www_default | head -n 160 | tail -n 20
(略)
            "[ID]": {
                "Name": "[container_name]",
                "EndpointID": "~~~",
                "MacAddress": "~~~",
                "IPv4Address": "~~~",
                "IPv6Address": ""
            }
(略)

上のコンテナは既にdocker rmで削除していますので、networkにゾンビのように残ってしまっていることがわかります。

あとheadとかtail使わなくてもjqとかdocker network –formatとかで綺麗に見られると思います。。

追記(2017/03/30)

swarmでノードを強制的に落とした時に同様のエラーが出ましたが、上記方法では解決しませんでした。

このような場合、落としたノードをクラスタに復帰させてからコンテナを削除する必要があるようです・・・