Docker daemon cannot start on Ubuntu 16.04 Linux kernel 4.4

My Docker version is Docker CE 18.06.1.

$ sudo apt list --installed | grep -i docker
docker-ce/xenial,now 18.06.1~ce~3-0~ubuntu amd64 [installed]

Start dockerd.

$ sudo systemctl start docker

I got an error in syslog and cannnot start dockerd.

systemd[1]: Failed to start Docker Application Container Engine.

The cause of this error is that dockerd uses aufs storage engine in systemd startup script. Ubunt 16.04 don’t have aufs module by default. You can install aufs module but if you use Linux kernel 4.0 >=, you can use overlay2. So you can change the storage driver to --storage-driver overlay2 from --storage-driver aufs.

/etc/systemd/system/docker.service.d/10-machine.conf

[Service]
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --storage-driver overlay2 --tlsverify --tlscacert /etc/docker/ca.pem --tlscert /etc/docker/server.pem --tlskey /etc/docker/server-key.pem --label provider=openstack

Start dockerd.

$ sudo systemctl start docker

How to change Timezone on Docker with Ubuntu 16.04

Ubuntu 16.04 and every other use UTC by default.

I want to change to JST. So I search about this on the web, and some site said to use like this.

Show list of time zone

$ timedatectl list-timezones

But I executed this command, and then above message occurs.

Error about bus happen

$ timedatectl list-timezones
Failed to create bus connection: No such file or directory

It is a problem to use systemd. Ubuntu has used systemd since 16.04.

To use systemd on docker container is pretty tough. So I used tzdata. It is still available to install via apt on Ubuntu 16.04, but if you can also change time zone with systemd. See this document.

I write this config in Dockerfile like this. And build the container.

Dockerfile

FROM ubuntu:16.04

RUN \
  apt-get -y install tzdata && \
  ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

Then I enter the container and check time zone.

root@4dbd2aff2747:~# cat /etc/timezone
Asia/Tokyo

root@9dce4012502e:~# strings /etc/localtime
TZif2
JCST
TZif2
JCST
JST-9

The docker container’s time zone becomes JST!

Docker Swarm modeでservice間のアクセスを可能にする

Docker Swarmのservice間で名前解決して互いにネットワークアクセスできるように設定する方法です。

使用したDocker engineのバージョンは 1.13.0 です。

service discoveryを使用する

service discovery を使用するとservice名で名前解決できるようになります。例えば appdb という2つのserviceを作成した時、app から jdbc:mysql://db:3306/mydatabase のように db に接続できるようになります。このように名前解決できる機能のことを service discovery と言います。

service discoveryは、例えばWebアプリケーションでHTTPSを使いため、WebアプリケーションのserviceとNginxのserviceを立ち上げて、Nginxをリバースプロキシとして使いたい場合を考えてみます。

Webアプリケーションのservice名は myapp、 Nginxのservice名は proxy とします。
Webアプリケーションはport 9000で動作していて、Nginxは外部のHTTPSのport 443を受け取ってport 9000にリバースプロキシします。

myapp(9000) <- (9000)proxy(443) <- Webブラウザ

この時Nginxをリバースプロキシとして使いたいので、以下のような設定をします。

upstream my-backend {
  server myapp:9000;
}

server myapp:9000の部分でmyappのポート9000に対してリクエストを転送していることがわかります。

このservice discoveryを使用するには、新規にoverray networkを作成する必要があります。
Attach services to an overlay network

overlay networkを作成する

以下はoverlay networkを作成する例です。

$ docker network create --driver overlay my-network

次にこのネットワークを追加したserviceを作成します。
以下はNginxと自分で作成したアプリケーションのserviceを作成しています。

Nginx

$ docker service create --replicas 1 -p 80:80 --name nginx --network my-network my-nginx

Myapp

$ docker service create --replicas 1 --name app --network my-network my-app

このように同じoverlay networkを設定してserviceを作成します。同じネットワークに存在するserviceはVIPとDNSが設定されて、service名に名前解決できるようになります。

By default, when you create a service attached to a network, the swarm assigns the service a VIP. The VIP maps to a DNS alias based upon the service name. Containers on the network share DNS mappings for the service via gossip so any container on the network can access the service via its service name.

Use swarm mode service discovery

実際に app serviceで作成したコンテナに入ってdig nginxなどすると名前解決されていることがわかります。

service discoveryのハマりどころ

動作に必要なポートが開いているか確認する

service discoveryを使用するにはPort 7946 TCP/UDP とPort 4789 UDP が開いていることが必要です。
もし同じoverlay networkを設定しているのに名前解決できない場合は、netstat -antupしてみます。上記のポートが見つからなければdocker daemonをrestartしてみると直ることがあります。
このportがlistenされない原因を調べてみましたが、結局よくわかりませんでした。新しいバージョンだと解決されているかもしれません。
このportがlistensされていなくてもDocker Swarmは警告を出さない(たぶん)ので気づきにくいです。

また、portは問題ないのにうまくservice discoveryできない場合はホストマシン間のportが拒否されていないか確認します。もしES2のようなserviceを使用している場合は、そちらのポートの設定も確認します。

serviceを起動する順番

serviceに依存している別のserviceを立ち上げる場合、立ち上げる順番に気をつける必要があります。

先の例だと、myapp を立ち上げる前に proxy を立ち上げてしまうと、myapp がまだservice discoveryに登録されていないので、 proxy service起動時に名前解決されずにエラーで起動できません。先に myapp serivceを立ち上げてから proxy serviceを立ち上げる必要があります。

このようにservice名に依存したserviceがある場合は、serviceを立ち上げる順番に気をつける必要があります。

また、Nginxの場合だとservice名を変数にして、service discoveryのDNSで名前解決する方法があります。この時resolver 127.0.0.11 valid=2s;でDNSのTTLに関わらず、2秒で更新するようにします。

serviceを起動する順番に依存しないようにする

resolver 127.0.0.11 valid=2s;
set $upstream "myapp";

upstream my-backend {
  server myapp:9000;
}

HTTPを使ったDocker Registryを作る

基本的にドキュメントに書いてありまが、Docker RegistryでHTTPを使おうとすると少し面倒だったので、まとめておきます。

プライベートなネットワークで使っているぶんにはHTTPで十分ですが、いくつか設定を追加する必要があります。

Docker Registryを起動する

Docker Registryを起動すにはDeploy a registry serverに書かれている通り、以下のコマンドを実行します。
Docker Registryはこれで終わりです。

$ docker run -d -p 5000:5000 --restart=always --name registry registry:2

Docker Registryに接続するクライアントの設定

こちらに書かれている通り以下のようにDockerの設定を追加します。

Docker Registry が10.0.0.2:5000でLISTENしている例です。

/etc/docker/daemon.json

{ "insecure-registries":["10.0.0.2:5000"] }

/etc/default/docker

DOCKER_OPTS="--insecure-registry 10.0.0.2:5000"

設定はこの2つです。

あとはDocker daemonをrestartします。

# systemdの場合
$ sudo systemctl restart docker.service

これでHTTPで接続できるようになります。

設定は簡単ですが、公式ドキュメントから設定方法を見つけられなかったので、設定に少し時間がかかりました。

Docker Swarm内の分散したログをFluentdでまとめてElasticsearchとKibanaでログを確認する

概要

Docker Swarmで複数のコンテナがあると、コンテナが起動している複数のnodeに入ってdocker logsは大変です。2台や3台程度であればdocker logsで十分かもしれませんが、5台やそれ以上になると大変です。
また、一番大きい問題としてコンテナのログはapplication imageを更新するたびに揮発しまうことがあります。

揮発対策のためにtaskを実行しているホストマシン上にファイル出力する方法もありますが、分散したnodeに入ってログを確認しなければいけない問題が解決しません。
そのため、Docker Swarmで複数のコンテナのログを一箇所に収集するようにします。

また一箇所に集めたログをlessなどで見てもいいですが、APIアクセスやエラーの統計を取れるようにしたいので、ElasticsearchとKibanaを使用します。

全体の流れは以下のようになります。

Docker container -> Fluentd -> Elasticsearch -> Kibana

  1. Docker containerからFluentdにログを送信
  2. FluentdでElasticsearchにログを送信するようにする。ここでElasticsearchのフォーマットに合わせてログを変換
  3. Kibanaでログを確認

Docker containerからFluentdにログを送信する

Dockerサービスを作成時に--log-driver=fluentdでFluentdに出力するように指定します。Dockerは標準でFluentdをサポートしています。次に--log-opt fluentd-address=127.0.0.1:24224で送信先とポートを指定します。--log-opt tag='developer_center.clientでFluentdのタグを指定します。

docker service create --replicas 1 --name api --log-driver=fluentd --log-opt fluentd-address=127.0.0.1:24224 --log-opt tag='developer_center.api' api

FluentdからElasticsearchにログを加工して送信する

Fluentdは0.12を使用しています。

NginxのログをElasticsearchに送信してみます。

Docker containerから送信されたログは以下のようにJSON形式になっています。

2017-07-19 11:54:27,165 [application-package-actor-7] INFO  application - make directories: mkdir -p /myapp/example/124223/50001

以下はFluentdでアプリケーションのログをパースしてElasticsearchに送信する設定です。

<filter application>
  @type parser
  format /^(?<time>.*) \[(?<thread>.*)\] (?<level>[\w]*)\s+(?<class>[\w.]*) - (?<message>.*)|\s*(?<system_message>.*)$/
  time_format %Y-%m-%d %H:%M:%S,%L
  key_name log
  keep_time_key true
  reserve_data true
</filter>

<filter application>
  @type record_transformer
  enable_ruby true
  <record>
    timestamp ${ require 'time'; Time.now.utc.iso8601(3) }
  </record>
</filter>

<match application>
  @type copy
  <store>
    @type elasticsearch
    host sample-s18kfo2pa387njeiowbf9km4oe38by.ap-northeast-1.es.amazonaws.com
    port 80
    logstash_format true
    logstash_prefix application
    time_key timestamp
    buffer_type file
    buffer_path /dev/shm/fluentd_buffer/application.buf
    buffer_queue_limit 128
    buffer_chunk_limit 32m
    flush_interval 1s
  </store>
  <store>
    @type file_alternative
    path /home/fluentd/logs/application.log
    time_slice_format %Y%m%d%H00
    time_format %Y-%m-%dT%H:%M:%SZ
    buffer_type file
    buffer_path /dev/shm/fluentd_buffer/application.buf
    buffer_queue_limit 64
    buffer_chunk_limit 32m
    flush_interval 1s
    flush_at_shutdown
    add_newline true
  </store>
</match>

最初のfilterではアプリケーションのログをパースします。formatはパースするフォーマットです。ここではアプリケーションのログフォーマットに合わせています。time_formatも同じように時刻のフォーマットを合わせています。key_name logはどのJSONのkeyをパース対象にするか指定します。ここでは log を対象にしています。reserve_data truekey_name logで指定した元のログをパース後も保持するか指定します。 false にするとkeyが log のレコードが削除されます。

2番目のfilterではパースしたレコードにElasticsearchで使用するタイムスタンプを追加しています。タイムスタンプはすでにあるのですが、Fluentd 0.12はミリ秒に対応していないので、ここでrubyのTimeを使ってログを処理した時刻にミリ秒を含めたタイプムスタンプをレコードに追加しています。

matchではElasticsearchに送信する設定とローカルディレクトリにログを保存する設定です。

1番目の<store>はElasticsearchに送信する設定です。ここでElasticsearchに送信するための設定としてhostport指定しています。次にlogstash_formatlogstash_prefixを指定しています。logstash_formatはKibanaで加工しやすいようにインデックスを付けます。logstash_prefixではインデックスのprefixを指定します。Kibanaでインデックスを指定してログを見ることになるのでわかりやすい名前をつけます。上記の設定ではclient-2017.01.01のようなインデックスが作成されます。また、time_key timestampfilterで追加したタイムスタンプを使用するようにkeyを指定しています。

2番目の<store>はローカルディレクトリにログを保存する設定です。
詳しい設定はこちらを参照してください。

ElasticsearchとKibanaを準備する

ElasticsearchとKibanaを使用するにはAWSで提供されているAmazon Elasticsearch Serviceを使用しています。インスタンスを立ち上げるだけで使用できるようになるのでとても簡単です。もちろん、Elasticsearchにログを流す前にインスタンスを立ち上げておく必要があります。

Kibanaでログを確認する

Kibana上で、Management -> Index Patterns -> [Add New]でindex patternを指定します。

Index name or patternにはlogstash_prefixで指定した値を入力します。
正しくログが入っているとTime-field name@taimestampを選択できるようになるので、選択して[Create]します。

あとは、Discover から先ほど登録したindex patternを指定すればログを確認できるようになります。

課題

複数行になるログ(スタックトレースなど)をKibanaで見るとタイムスタンプが同じになってしまい、本来の行とは異なった並びになってしまうことがあります。複数行のログをうまく一つにまとめられるとログが見やすくなると思います。何か方法はないかな…

Dockerとnet.ipv4.conf.all.forwarding

Docker containerから外部へのアクセスができなくなった時の原因と直し方です。

何が起こったか

centosをリスタートかsystemctl restart network.serviceすると net.ipv4.conf.all.forwarding0 になってしまいます。

$ sysctl net.ipv4.conf.all.forwarding
net.ipv4.conf.all.forwarding = 0

net.ipv4.conf.all.forwarding が0になってしまうとDockerコンテナと外のネットワークのパケットのforwardingができなくなり、外部とのネットワークが繋がらない状態になります。
ドキュメントにも書かれています。

これはDocker imageをbuildするときも同じです。

CIでこれをやってしまい、Docker imageのbuild時にyum installしている部分でネットワークに接続されず、かつ必要なパッケージがインストールされずにDocker imageがbuildされてしまいました。
ログを見ると warning は出ているのですが error ではないためbuildは継続され、正常にDocker imageが作成されます。アプリケーションを実行すると実行に必要なパッケージがインストールされていないため、アプリケーション側でエラーが出ます。

下記はnet.ipv4.conf.all.forwarding が0の状態でDocker buildをした出力の一部です。

3行目で---> [Warning] IPv4 forwarding is disabled. Networking will not work.が出力されていることから、IPv4 forwardingが無効になっていることがわかります。また、 Removing intermediate container c0b08184ced5でもわかるようにbuildは続行されています。

ネットワークエラーでパッケージはインストールされないがDocker buildは続行される

Step 3/5 : RUN yum -y update; yum -y install rsync; yum clean all; yum install -y openssh-clients
---> [Warning] IPv4 forwarding is disabled. Networking will not work.
---> Running in c0b08184ced5
Loaded plugins: fastestmirror, ovl

One of the configured repositories failed (Unknown),
and yum doesn't have enough cached data to continue. At this point the only
safe thing yum can do is fail. There are a few ways to work "fix" this:

1. Contact the upstream for the repository and get them to fix the problem.

2. Reconfigure the baseurl/etc. for the repository, to point to a working
upstream. This is most often useful if you are using a newer
distribution release than is supported by the repository (and the
packages for the previous distribution release still work).

3. Run the command with the repository temporarily disabled
yum --disablerepo= ...

4. Disable the repository permanently, so yum won't use it by default. Yum
will then just ignore the repository until you permanently enable it
again or use --enablerepo for temporary usage:

yum-config-manager --disable
or
subscription-manager repos --disable=

5. Configure the failing repository to be skipped, if it is unavailable.
Note that yum will try to contact the repo. when it runs most commands,
so will have to try and fail each time (and thus. yum will be be much
slower). If it is a very temporary problem though, this is often a nice
compromise:

yum-config-manager --save --setopt=.skip_if_unavailable=true

Cannot find a valid baseurl for repo: base/7/x86_64
Could not retrieve mirrorlist http://mirrorlist.centos.org/?release=7&arch=x86_64&repo=os&infra=container error was
12: Timeout on http://mirrorlist.centos.org/?release=7&arch=x86_64&repo=os&infra=container: (28, 'Resolving timed out after 30544 milliseconds')
Loaded plugins: fastestmirror, ovl

---> d6d1bd8a8271
Removing intermediate container c0b08184ced5

原因

これが発生する原因としては2つあります。

network serviceのscript

起動時のスクリプトでnet.ipv4.ip_forward=0を実行しています。

/etc/init.d/network

...
stop)
sysctl -w net.ipv4.ip_forward=0 > /dev/null 2>&1
...

networkをリスタートした時sysctl -w net.ipv4.ip_forward=0が実行されます。

動作の確認

$ sudo sysctl net.ipv4.conf.all.forwarding=1
net.ipv4.conf.all.forwarding = 1
$ sudo systemctl restart network.service
$ sysctl net.ipv4.conf.all.forwarding
net.ipv4.conf.all.forwarding = 0

/etc/sysctl.conf

/etc/sysctl.confnet.ipv4.ip_forward=0を設定している場合でも0になってしまいます。

/etc/sysctl.conf

net.ipv4.ip_forward=0

動作の確認

$ sudo sysctl net.ipv4.conf.all.forwarding=1
net.ipv4.conf.all.forwarding = 1
$ sudo systemctl restart network.service
$ sysctl net.ipv4.conf.all.forwarding
net.ipv4.conf.all.forwarding = 0

対策

/etc/sysctl.confnet.ipv4.ip_forward=1を設定します。

/etc/sysctl.conf

net.ipv4.ip_forward=1

Docker buildでも問題なくビルドされるので、少し気づきにくい不具合でした。
ホストOSをリスタートしたりnetwork serviceをリスタートするときは設定を確認すると良いかもしれません。