wiki:linux/docker

Version 37 (modified by yuna, 9 years ago) (diff)

--

Docker

個人的なメモなので、Dockerを勉強したい場合、ここより他のサイトを見ることをお勧めします。

Ubuntuで最新のDockerを使う

ここみる  http://www.ubuntuupdates.org/ppa/docker

最新版のDockerは、Dockerプロジェクトでパッケージを配布している。ここみるとよい  http://blog.docker.com/2015/07/new-apt-and-yum-repos/ Key Serverにうまく接続できないときは、下記のようにubuntuのKey Serverを利用する。Ubuntu 14.04(trusty)の例だと下記のようになる。

# apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
# echo "deb https://apt.dockerproject.org/repo ubuntu-trusty main" >> /etc/apt/sources.list
# apt-get update
# apt-get install docker-engine

設定について

プロキシ

http_proxyやDNSの設定は、/etc/default/docker(Ubuntu) or /etc/sysconfig/docker (RHEL)でやる。http_proxyは設定しておかないとProxy環境ではdocker search/pullが使えない。 DNSをきちんと設定しておかないと、サーバプロセスが遅くなることがあるので注意

ストレージドライバ

Ubuntuでは、デフォルトのストレージドライバがaufs。overlayfsを試したい場合は、次のようにする。

/etc/default/docker
DOCKER_OPTS="-s overlay"

設定後dockerデーモンを再起動。

# service docker restart

aufsで利用していたイメージ、コンテナは全て消えるので、注意が必要。イメージの移行は下記のようにする。

# docker ps
CONTAINER ID        IMAGE               ...
1869db53a211        mariadb             ...
..
# docker commit 1869db53a211 export/server1 
# docker save export/server1 > export_server1.tar.gz
(ドライバ変更後)
# docker load < export_server1.tar.gz

Dockerfile

Dockerイメージを作るファイル

  • COPYは使わずADDを使う。ADDの方がURLを指定したり、アーカイブを自動解凍したり高機能。
  • ENTRYPOINT/CMDは引数無しで実行したときのデフォルトの実行コマンド。CMDは上書き可能でENTRYPOINTは上書きできない。
    • 汎用的なイメージを作りたい場合はCMD。アプリケーションコンテナとしてユーザに勝手な操作をさせないならENTRYPOINT
    • ただしENTRYPOINTを利用した場合でも、ENTRYPOINTとして利用するファイルは変更可能

プロキシが必要な環境でイメージのビルドを行いたい場合、ENVコマンドを利用することにより、ビルド時の環境変数を設定することができます。

ENV http_proxy http://myproxy.server.com:8080
ENV https_proxy http://myproxy.server.com:8080

ネットワーク

DockerfileにEXPOSEを記述して、公開するポートを指定。もしくは、--expose <port>でポートを公開。

Dockerのホスト以外からアクセスする場合は、-p <ホストのポート>:<コンテナのポート>オプションでホストのポートを使って公開。例えば、SSHのポートをホストの2022番でアクセスできるようにするには、下記のようになる。

# docker run -it --name foo --expose 22 -p 2022:22 ubuntu /bin/bash

起動済みのDockerのポートの公開

Docker自身には機能はありません。ホスト上でiptablesを利用して直接設定してやります。例えば、上記のコンテナ上の22番をホスト上の2022番に公開するには、次のようにします。

まず、コンテナのIPアドレスを調べます。

# docker inspect コンテナ名  |grep IPAddress
        "IPAddress": "172.17.0.18",

上記で調べたコンテナに対して、直接iptableを設定します。

# iptables -t nat -A DOCKER ! -i docker0 -p tcp -m tcp --dport 2022 -j DNAT --to-destination 172.17.0.18:22
# iptables -t filter -A DOCKER -d 172.17.0.18/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 22 -j ACCEPT

イメージ初期化のChips

Tipsですね。ポテトチップスではありません。

  • expect: コマンドラインでインタラクティブな設定を行う場合利用
  • supervisord: Dockerではsystemdが使えないので、supervisordなどでデーモンを起動するとよい

Dockerでベンチマーク

MariaDBでDockerのベンチマークを取ってみる。

コンテナ取得、起動

# docker pull mariadb
# docker run --name mariadb1 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password  -d mariadb

ベンチマークツールインストール

# apt-get install sysbench mariadb-client

ベンチマーク準備

# mysqladmin -h 192.168.1.5 -u root --password=password create sbtest
# sysbench --test=oltp --oltp-table-size=200000 --max-time=300 --max-requests=0
  --mysql-host=192.168.1.5 --mysql-port=3306 --mysql-table-engine=InnoDB
  --mysql-user=root --mysql-password=password --mysql-engine-trx=yes --num-threads=5
  prepare

ベンチマークをやり直し場合、mysqladmin dropでsbtestを削除してからやり直す。

ベンチマーク実行

# sysbench --test=oltp --oltp-table-size=200000 --max-time=300 --max-requests=0
  --mysql-host=192.168.1.5 --mysql-port=3306 --mysql-table-engine=InnoDB 
  --mysql-user=root --mysql-password=password --mysql-engine-trx=yes --num-threads=5
  run

コンテナの再開

docker startを利用すると既存のコンテナを再開することができます。コンテナ再開時の初期化処理は、docker runで指定した環境変数はそのままに、/entrypoint.shが実行されます。

Dockerのbashに接続

Dockerコンテナ内のbashに接続するには、docker attachを利用する。

Dockerでサービスを利用

systemdを利用

CentOS7のDockerイメージは、systemd-containerというパッケージがインストールされており、systemdを利用してコンテナ内のサービスを起動することができます。

CentOS7のsystemdのサービスを利用するには、下記のコマンドで/sbin/initを利用する。

# docker run --name mycon --privileged -it hoge /sbin/init

コンテナにbashで乗り込んでサービスを有効にする。

# docker exec -it mycon /bin/bash
# service enable httpd
# service mariadb
# exit

コンテナを終了、コミットしイメージを作成

# docker stop mycon
# docker commit mycon myserv

作成したイメージで、/sbin/initを起動するとサービスが起動する。CMDやENTRYPOINTを利用してもよい。

# docker run --privileged myserv  /sbin/init

upstartを利用してサービスを起動

Ubuntuでupstartを利用してサービスを起動するサンプルを下記の場所にあります。

 https://github.com/tianon/dockerfiles/tree/4d24a12b54b75b3e0904d8a285900d88d3326361/sbin-init/ubuntu/upstart/14.04

supervisordを利用してサービスを利用

上記のsystemdを利用する方法は、特権が必要となり、あまりセキュアではありません。また、CentOSはsystemd、Ubuntuはupstartとサービスの起動方法が違っては、 OS毎の設定が面倒です。supervisordを利用すると、特権を利用せず、また、統一的な方法でdocker内でサービスを管理することができます。

# apt-get install python-setuptools
# easy_install supervisor
# echo_supervisord_conf > /etc/supervisord.conf

上記supervisord.confにサービスの設定を記述

[supervisord]
nodaemon=true

[program:mariadb]
command=/usr/bin/mysqld_safe
autostart=true
autorestart=true

[program:httpd]
command=/usr/local/bin/pidproxy /var/run/apache2/apache2.pid /bin/bash -c "source /etc/apache2/envvars && /usr/sbin/apache2 -DFOREGROUND"
redirect_stderr=true

イメージをコミットして、supervisordをコンテナで起動すれば、mysqlとapacheが起動します。

# docker run image /usr/local/bin/supervisord

イメージ・コンテナファイルの場所を変更

Dockerはデフォルトでは、/var/lib/dockerにコンテナやイメージファイルを格納している。別のディレクトリに保存したい場合、/var/lib/dockerをシンボリックリンクにすることが考えらえるが、シンボリックリンクではうまく動作しない。bind mountを利用するか、/etc/default/dockerに-gオプションを追加して、dockerのディレクトリを指定する必要がある。

bind mountを利用する場合、/etc/fstabに次のように記述するとよい。

/mnt/disk2/docker  /var/lib/docker         none    defaults,bind   0       0

コミットせずにコンテナ設定を変える

基本的にコンテナの設定を変えるためには、コンテナを一度コミットして、起動し直す必要があります。 コンテナのディスクサイズが大きくなると、コミットするのにも時間がかかります。

コンテナ設定ファイルのconfig.jsonを編集することで、コンテナの設定を変更することができます。 config.jsonファイルを編集する際の注意として、dockerサービスを停止しておく必要があります。 config.jsonの内容はdockerサービス内でキャッシュされているので、停止してから編集する必要があります。 また、サービスの停止に

# service docker stop

コンテナの設定ファイルは、/var/lib/docker/containers/コンテナID/config.jsonにあります。コンテナ実行時のコマンドを変更する場合は、下記のPathとArgsの値を変更します。

"Created":"2015-08-22T11:30:36.507268698Z","Path":"/bin/bash","Args":[],"Config":

修正後、dockerサービスを開始し、コンテナをstartすると変更したコマンドでコンテナが起動されます。

ネットワークの設定は、config.jsonとhostconfig.jsonに記載されています。例えば、コンテナの80をホストの18080に設定する例(docker run --expose 18080 -p 18080:80の例)

config.json

"ExposedPorts":{"18080/tcp":{},"80/tcp":{}},

hostconfig.json

"PortBindings":{"80/tcp":[{"HostIp":"","HostPort":"18080"}]}

コンテナのルートディレクトリの場所

コンテナのルートディレクトリの場所は、利用するストレージドライバ(利用するファイルシステムの実装)により異なる。

ストレージドライバ 場所
overlay(overlay fs) /var/lib/docker/overlay/[コンテナID]/merged
aufs /var/lib/docker/aufs/mnt/[コンテナID]

devicemapperの場合は、/var/lib/docker/devicemapper/devicemapper/[コンテナID]にボリューム本体とメタデータがある。コンテナ実行中であれば、/var/mapper/docker-xxxx:x-xxxxxxxxxx-[コンテナID]を利用できる。

トラブルシューティング

パッケージがインストールできない

CentOS7のコンテナ上で、下記のようなエラーメッセージと共にパッケージがインストールできないことがある(下記はhttpdの例)。

Downloading packages:
httpd-2.4.6-31.el7.centos.1.x86_64.rpm                                                               | 2.7 MB  00:00:03
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : httpd-2.4.6-31.el7.centos.1.x86_64                                                                       1/1
Error unpacking rpm package httpd-2.4.6-31.el7.centos.1.x86_64
error: unpacking of archive failed on file /usr/sbin/suexec: cpio: cap_set_file
  Verifying  : httpd-2.4.6-31.el7.centos.1.x86_64                                                                       1/1

Failed:
  httpd.x86_64 0:2.4.6-31.el7.centos.1

Complete!

Ubuntu14.04のカーネルはバージョンが古く、AUFSに機能が不足しているためエラーが発生することがある。ホストのカーネルを最新版にアップデートすることにより、解決する。

バージョンアップ後dockerサービスが起動できない

Dockerをバージョンアップ後、Dockerが起動しなくことがあります。コマンドラインから-dオプションでデーモンを起動しようとして、 次のようなメッセージが出た場合、

# docker  -d
Warning: '-d' is deprecated, it will be removed soon. See usage.
WARN[0000] please use 'docker daemon' instead.
INFO[0000] Listening for HTTP on unix (/var/run/docker.sock)
FATA[0000] Error starting daemon: error initializing graphdriver: "/var/lib/docker" contains other graphdrivers: devicemapper; Please cleanup or explicitly choose storage driver (-s <DRIVER>)

/var/lib/docker/devicemapper ディレクトリを削除すると、サービスが起動するようになります。devicemapperで起動していたコンテナは削除されるので、古いバージョンに戻りexportでコンテナの移行などをしてください。

Swarmの設定

詳しくは[ここ  https://docs.docker.com/swarm/install-manual/]に書いてある。構成は、次のような構成を考えておく

swarm    consul  swarm      consul  swarm
manager  server  join       agent   join
+------------------------+  +---------------------+
| DockerHost1            |  | DockerHost2         |   ...
| 192.168.100.1          |  | 192.168.100.2       |
| 管理ノード&Dockerホスト|  | Dockerホスト        |
+------------------------+  +---------------------+

DockerHost?1は管理ノード兼Dockerホスト、DockerHost?2はDockerホストとする。管理ノードをDockerホストとして利用しないのであれば、DockerHost?1上のswarm joinは不要。

事前準備

最初にクラスタのトークンを作成する。

# docker pull swarn
# docker run --rm swarm create
a8440712cd4709e5edbf701506189c3c

以下、ノードを起動する際には、上記のトークンを利用するので、メモしておく。

VMコピー時の注意

VMをコピーしてDockerホストを複数作成すると正しくクラスタに登録できない。 /etc/docker/key.jsonファイルでDockerは自身のIDを識別するようになっている。 VMをコピーしてDockerホストを作成した場合、下記のように、キーを削除して Dockerを再起動する。

# rm /etc/docker/key.json
# service docker restart

管理ノード(consulサーバの起動)

次のようなconsule-server.jsonファイルを作成し、consulサーバを起動する。

{
  "datacenter": "docker",
  "addresses" : {
    "http": "0.0.0.0"
  },
  "bind_addr": "192.168.100.1",
  "node_name": "dockerhost1",
  "domain": "consul",
  "server": true,
  "bootstrap_expect": 1,
  "data_dir": "/var/lib/consul"
}
# mkdir -p /var/log/consul
# consul agent -config-file consul-server.json  >> /var/log/consul/consul.log

各ノードの設定

Consulクライアントの設定

次のようなconsule-client.jsonファイルを作成し、consulエージェントを起動する。bind_addrには、Dockerホストの各IPを設定する。 (consulサーバを起動したノードでは実行しないので注意!)a

{
  "datacenter": "docker",
  "addresses" : {
    "http": "0.0.0.0"
  },
  "bind_addr": "192.168.100.2",
  "node_name": "dockerhost2",
  "domain": "consul",
  "retry_join": [ "192.168.100.1"],
  "server": false,
  "data_dir": "/var/lib/consul"
}
# mkdir -p /var/log/consul
# consul agent -config-file consul-client.json  >> /var/log/consul/consul.log

/etc/default/dockerに下記の設定を追加しホストの2375ポートを コンテナにバインドできるようにする。

DOCKER_OPTS="--cluster-store=consul://localhost:8500 --cluster-advertise=192.168.100.1:2376 -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock"

Swarmエージェントを起動する。ホストIDは、ノードのIP。

# docker run -d swarm join --addr=<ホストIP>:2375 token://<トークンID>

例えば、次のような感じ。

# docker run -d swarm join --addr=192.168.100.2:2375 token://a8440712cd4709e5edbf701506189c3c

管理ノード(クラスタマネージャの設定)

管理ノードで下記のコマンドを実行し、クラスタマネージャを起動する。 Dockerホストのどこかのノードでもよい。

# docker run -d -p 2376:2375 swarm manage token://a8440712cd4709e5edbf701506189c3c

動作確認

起動できたら下記のコマンドでクラスタの動作を確認できる。

# docker -H tcp://192.168.100.1:2376 info
Containers: 9
Images: 4
Role: primary
Strategy: spread
Filters: health, port, dependency, affinity, constraint
Nodes: 2
 dockerhost1: 192.168.100.1:2375
  └ Status: Healthy
  └ Containers: 8
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 1.018 GiB
  └ Labels: executiondriver=native-0.2, kernelversion=3.19.0-28-generic, operatingsystem=Ubuntu 14.04.1 LTS, storagedriver=aufs
 dockerhost2: 192.168.100.2:2375
  └ Status: Healthy
  └ Containers: 1
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 1.018 GiB
  └ Labels: executiondriver=native-0.2, kernelversion=3.19.0-28-generic, operatingsystem=Ubuntu 14.04.1 LTS, storagedriver=aufs
CPUs: 2
Total Memory: 2.037 GiB
Name: 49a004525d21

Dockerでnetperfを使う

Dockerコンテナ上でnetserverを使うと、netperfは勝手にポートを確保してlistenするのでうまく動作しない。netperfコマンドで利用するポートを固定し、コントロールポートとデータポートの両方をできるようにすると解決できる。

コンテナを起動するときに、コントロールポート(デフォルト12865)とデータポート(netperfのオプションで指定ここでは12866)を利用できるようにして起動する。

host# docker run --exporse 12865-12866 --expose 12865-12866/udp -p 12865-12866:12865-12866 -p 12865-12866:12865-12866/udp netperf /bin/bash
container # netserver

netperfを負荷をかけたいホストから実行する。このときに、--のあとに-Pオプションでデータポートを指定する。

# netperf -H <コンテナを起動したホスト> -t TCP_STREAM -- -P 12866

Kubernates

Kubernatesを動かしたメモ。ここ見た方がいいかも:  https://github.com/kubernetes/kubernetes/blob/master/docs/getting-started-guides/docker.md

K8S_VERSION=1.1.1

docker run \
  --name etcd \
  -v /var/etcd:/var/etcd \
  --net=host -d\
  gcr.io/google_containers/etcd:2.0.12 \
  /usr/local/bin/etcd \
    --addr=127.0.0.1:4001 \
    --bind-addr=0.0.0.0:4001 \
    --data-dir=/var/etcd/data

docker run \
     --name=k8s_master \
     --volume=/:/rootfs:ro \
     --volume=/sys:/sys:ro \
     --volume=/dev:/dev \
     --volume=/var/lib/docker/:/var/lib/docker:ro \
     --volume=/var/lib/kubelet/:/var/lib/kubelet:rw \
     --volume=/var/run:/var/run:rw \
     --net=host \
     --pid=host \
     --privileged=true \
     -d \
     gcr.io/google_containers/hyperkube:v${K8S_VERSION} \
     /hyperkube \
       kubelet \
        --containerized \
        --hostname-override="127.0.0.1" \
        --address="0.0.0.0" \
        --api-servers=http://localhost:8080 \
        --config=/etc/kubernetes/manifests \
        --allow-privileged=true --v=10

docker run \
     --name k8s_proxy \
     --net=host --privileged \
     -d \
     gcr.io/google_containers/hyperkube:v${K8S_VERSION} \
     /hyperkube \
       proxy \
       --master=http://127.0.0.1:8080 --v=2
# ./kubectl get nodes
NAME        LABELS                             STATUS
127.0.0.1   kubernetes.io/hostname=127.0.0.1   Ready