現在位置: ホーム / OSSブログ / Dockerも始めました

Dockerも始めました

以前Vagrantについて書きましたが、今回はもう1つの注目仮想技術である「Docker」について調べた記事となります。

 こんにちは、北野です。前回はVagrantについて書きましたが、今回はもう1つの注目仮想技術である、「Docker」について調べてみました。

Dockerとは

  Docker は、Linuxのコンテナ系技術を利用してアプリケーションのビルド、デプロイ、実行を行うコンテナ管理ソフトウェアです。Docker社(旧dotCloud社)のSolomon Hykes氏によってGo言語で作成され、Apacheライセンスで配布されているオープンソースソフトウェアです(ソースコードは、https://github.com/docker/docker にあります)。

 Dockerは2014年6月のバージョン1.0のリリースを機に名称変更が行われ、前述した技術(多くの人がDockerと呼んでいるコンテナ管理技術)は、現在は「Docker Engine」が正式名称となっています。このDocker Engineに、アプリケーションの開発や配布を行うためのWebサービス「Docker Hub」、API、サードパーティによるエコシステムを含めたプラットフォーム全体の総称が「Docker」となりました。

 ただし、本稿では表記簡略化のため、特に断りがない限り、Docker Engineを単にDockerとして表記しています。

なぜ今Dockerなのか

 Dockerは、「コンテナ型仮想化」と呼ばれる仮想化技術を利用しています。これは、プロセスなどのリソースを「コンテナ」という単位ごとに隔離することで異なる環境が動作しているように見せる、という仮想化技術です。

 


 仮想マシン型仮想化(上)とコンテナ型仮想化(下)の比較(Docker社の公式サイトより引用)

 コンテナ型仮想化そのものは特に目新しい技術ではなく、古くはファイルシステム隔離による仮想化を実現していたchrootまでその源流を遡ることができます。また、近年でもホスティングサービスのVPS(Virtual Private Server:仮想専用サーバー)と呼ばれる、1台のサーバー上に複数ユーザーが自身の(仮想的な)専用サーバーとして利用できるプランで、OpenVZベースのVirtuozzoやFreeBSD Jailなどのコンテナ技術が利用されてきました。

 そして、クラウドから開発環境まで仮想化全盛の昨今、これまでの主流方式だった仮想マシン型仮想化と比べて「動作が軽量」なコンテナ型仮想化に再び注目が集まり、新しい活用法が編み出されています。その最前線にいる企業のひとつがGoogle社で、2014年5月に開催されたGlueConというイベントにおいて、Google Cloud Platform担当のシニアスタッフソフトウェアエンジニア Joe Beda氏が「Googleのすべてのサービスをコンテナ上で運用している」(週に20億個のコンテナを起動)と発表し、コンテナ型仮想技術がすでに十分に実用的な技術となっていることを示しました。



 Joe Beda氏の発表スライド「Containers at Scale」より1ページ目引用

 最近になってDockerが注目を集めているのは、「動作の軽量さ」という特長をもつコンテナ型仮想化技術への関心の高まりに加え、コンテナの差分イメージ共有やキャッシュ利用による「環境構築の容易さ」というDocker独自の技術を兼ね備えている点にあるのではないかと思います。

Dockerが利用しているコンテナ系技術

 冒頭でも述べたとおり、DockerはLinuxのコンテナ系技術を利用しています。1つのLinuxカーネルを複数のコンテナで共用しているため、ホストとコンテナが異なるディストリビューションでも動作させることができます。ただし、当然ながらWindowsのようにカーネルが異なるOSを動かすことはできません。

 Dockerが利用しているLinuxのコンテナ系技術は、次のとおりです。

Namespaces

目的カーネル機能
プロセス空間の分離 PID Namespace(Kernel 2.6.24)
ファイルシステムの分離 MNT Namespace(Kernel 2.4.19)
プロセス間通信の分離 IPC Namespace(Kernel 2.6.30)
ホスト名の分離 UTS Namespace(Kernel 2.4.19)
ネットワークの分離 NET Namespace(Kernel 2.6.29)
ユーザーの分離 User Namespace(Kernel 2.6.23~Kernel 3.8)

 

Cgroup

目的カーネル機能
CPUの利用割合 Cgroup(cpu)
動作するCPUの制限 Cgroup(cpuset)
メモリ量の制限 Cgroup(memory)
デバイスの制限 Cgroup(device)

 

Storage

目的カーネル機能
ストレージドライバ Device Mapper、aufs、brtfsなど

 

network

目的カーネル機能
ホストとコンテナ間の通信 veth
仮想的なブリッジ bridge
通信制御 iptables

 

Security

目的カーネル機能
ホストとコンテナ間の通信 veth
プロセスの特権制御 Capability
アクセス制御 SELinux
システムコール制御 seccomp

 

 1つのコンテナには1つのコントロールグループが割り当てられるため、Cgroupの仕組みを利用してCPUやメモリの使用量を制限することができます。Dockerのコンテナを作成&起動するdocker runコマンドのヘルプにも、Cgroupを利用してCPUとメモリ使用を制御するオプションが掲載されています

$ docker run --help | grep -E "\-c,|\-m,"
  -c, --cpu-shares=0         CPU shares (relative weight)
  -m, --memory=              Memory limit

 また、Dockerは複数のコンテナのファイルシステムの管理にunion mountという技術を利用しています。union mountは、通常のマウントと異なり、マウントした下層にあるファイルシステムも透過的に参照できる特殊なマウントです。これによりコンテナの差分管理を行うことができます。

 aufsはLinux上でこのunion mountを実現するための実装の1つで、少し前まではDockerといえばaufsという図式が成立していました。しかし、aufsがLinuxカーネルに含まれていないため、Docker 0.7以降よりブロックデバイスレベルの実装であるDevice Mapper、ファイルシステムレベルの実装であるbtrfs、overlayfsなども選択できるようになっています。どのファイルシステムを利用しているかは、ディストリビューションによって異なります。

 


 コンテナの差分管理とunion mountの関係(Docker社の公式サイトより引用)

インストール方法

 Linuxなら、Dockerのインストールは下記の1行を実行するだけです。

RHEL/CentOS

$ sudo yum install docker

Debian/Ubuntu

$ sudo apt-get install docker.io [1]

 インストール後にバージョンが表示されたら、インストール完了です。

$ docker --version
Docker version 1.6.0, build 8aae715/1.6.0

 なお、dockerサービスを起動させていないと次のようなエラーがでます。サービスが起動していない場合は起動させてください。また、sudoコマンドが必要な環境でsudoコマンドをつけないで実行したときも同様のエラーが出ることがありますので、その際はsudoコマンドを付加して実行してください。

$ docker version
……
FATA[0000] Get http:///var/run/docker.sock/v1.18/version: dial unix /var/run/docker.sock: no such file or directory. Are you trying to connect to a TLS-enabled daemon without TLS?

 WindowsやMac OS Xの場合は、Boot2Dockerを利用するのが一般的なようです。Boot2Dockerは、VirtualBox上でTiny Core LinuxというLinuxディストリビューションを動かして、その上でDockerが動くようになっています。

[1] Debian/Ubuntuには単なるdockerパッケージもあり、そちらはパネル用のアプリケーションなので間違ってインストールしないように注意してください。

Dockerの基本的な使い方

 Dockerでの基本的な使い方は、Dockerコンテナのひな形となるDockerイメージをダウンロードして、それを元に実体となるコンテナを作成&起動します。コンテナへの変更を保存が不要ならそのまま終了、変更の保存が必要ならDockerイメージに書き出します。この流れを、dockerコマンドとともに図示すると次のようになります。

 


 Dockerコンテナのライフサイクルとdockerコマンドの対応関係

コマンドによる実行例

 では、今回はコンテナ利用に特化したLinuxディストリビューション「CoreOS」上で、CentOS 7のコンテナを起動してみます。

  1. Dockerイメージの検索

    Dockerイメージは、「Dockerレジストリ」と呼ばれるイメージ配布サイトからダウンロードして使うのが一般的です。デフォルトではDocker社が提供している公式Dockerレジストリである、Docker Hub Registryを参照するようになっています。

    では、 docker searchコマンドで、CentOSのイメージを探してみます。なお、CoreOSではdockerコマンドの実行にsudoコマンドは不要でしたが、CentOSなどのディストリビューションではsudoコマンドが必要となるので注意してください。

    core@core-01 ~ $ docker search centos
    NAME                                 DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
    centos                               The official build of CentOS.                   1043      [OK]
    
  2. Dockerイメージのダウンロード

    Dockerイメージは、「ユーザー名/OS名」という名前になっていますが、公式イメージはOS名しか書かれていません。目的のDockerイメージが見つかったら、docker pullコマンドで、ダウンロードします。centos:6.0のようにコロンのあとにバージョン番号を指定することもできますが、バージョンを指定していない場合はcentos:latestを指定したのと同じ、つまり最新版がインストールされます。

    core@core-01 ~ $ docker pull centos
    
  3. Dockerコンテナの作成&起動

    ダウンロードしたDockerイメージからdocker runコマンドでコンテナを作成&起動します(それぞれを個別に行いたいユーザーのために、作成だけならdocker create、起動だけならdocker startというコマンドも用意されています)。

    -iはコンテナの標準入力を開き、-tttyを確保します。そして最後にbashを指定することより、シェルを利用したインタラクティブな操作が可能となります。

    core@core-01 ~ $ docker run -it centos:latest bash
    
  4. 起動確認

    プロンプトが切り替わり、コンテナ内にログインできたことがわかります。ちゃんとCentOSがインストールされているかを確認してみます。

    [root@7c2242fe6ace /]# cat /etc/redhat-release
    CentOS Linux release 7.1.1503 (Core)
    
    ホストOSはCoreOSなので、これが表示されたことで間違いなくCentOS 7.1がコンテナとして動いていることが確認できました。

なお、Docker 1.3以降なら、ログインしなくても、docker execコマンドで指定コマンドをコンテナで実行させることができます。私の環境ではコンテナIDでしか動作しませんでした。また、ついつい引数で渡しているLinuxコマンド全体(cat /etc/redhat-release)をダブルクォーテーションでくくりたくなりますが、そうしてしまうとエラーとなりますので注意してください。

core@core-01 ~ $ docker exec 7c2242fe6ace cat /etc/redhat-release
CentOS Linux release 7.1.1503 (Core)

 主要なdockerコマンドは次のとおりです。

コマンド機能
docker search word Dockerレジストリ内のイメージを検索
docker pull image_name 指定したDockerイメージのダウンロード
docker images ダウンロードしたDockerイメージの一覧
docker run image_name コンテナの作成&起動
docker ps 起動中のコンテナの一覧
docker ps -a (停止中も含む)全コンテナの一覧
docker start container_name コンテナの起動
docker stop container_name コンテナの停止
docker restart container_name コンテナの再起動
Ctrl+p & Ctrl+q [2] コンテナからのデタッチ
doker attach container_name コンテナへのアタッチ
docker rm container_name コンテナの削除
docker rmi image_name ダウンロードしたイメージの削除
docker commit container_name image_name Dockerイメージの作成
docker build . カレントディレクトリにあるDockerfileのビルド
docker build –no-cache . ビルドのやり直し
docker exec container_id command 任意のコマンドの実行

[2] Ctrlキーを押しながらpキーを入力したあと、Ctrlキーを離さずにqキーを入力します。

 

 Docker社の公式サイトには実際にコマンドを入力して、簡単な操作を学ぶことができるチュートリアルサイトが用意されています。すべてのコマンドを試せるわけではありませんが、インストール前に雰囲気だけでも知りたいという方には適しているかもしれません。

 


 公式サイトのDockerチュートリアルサイト

Dockerfileの利用

  dockerコマンドで、CentOS 7が動くコンテナを作成できました。今回は、パッケージのインストールなど何も行いませんでしたが、イメージに手を加えたものをこのあとも利用したいなら、docker commitコマンドでDockerコンテナから新しいDockerイメージを作成して、リポジトリに登録しておく必要があります。

 しかし、それでは手間がかかりますし、作業ミスをしてしまう可能性もあります。そこで、Dockerには、Dockerfileというファイルに構成情報を記述して自動作成する方法が用意されています。では、Dockerfileを利用して、nginxサーバーが起動しているコンテナを起動してみましょう。

  1. Dockerfileの作成

    次のようなDockerfileを作成します。

    FROM centos:centos6
    MAINTAINER xxxxxx@sios.com
    
    RUN yum update -y
    RUN yum install wget -y
    RUN wget http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm  ;\
        rpm -ivh nginx-release-centos-6-0.el6.ngx.noarch.rpm
    RUN yum install nginx -y
    ADD index.html /usr/share/nginx/html/
    
    ENTRYPOINT /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf
    

    何を行っているかはおおよそ見当がつくと思いますが、このファイルは「CentOS 6のDockerイメージをダウンロードしたあと、Nginxをインストールし、最後にNginxを起動する」よう記述されています。各行の先頭にあるDockerfileの「命令」の意味については、後述の表を見ながら確認してください。

    また、同じディレクトリに、中身が「sios」とだけ書かれたindex.htmlを用意しておき、サイト確認に利用します。

    $ cat index.html
    sios
    

    各ファイルの構成は次のとおりです。

    .
    |-- Dockerfile
    `-- index.html
    
  2. docker buildの実行

    Dockerfileのあるディレクトリをカレントディレクトリにしてから、docker buildを実行してイメージを作成します。-tはイメージ名(kitano/nginx)とタグ名(1.0)を指定しています。最後にカレントディレクトリを意味する「.」(ドット)を指定しています。

    $ docker build -t kitano/nginx:1.0 .
    
  3. コンテナの作成&起動

    続いて、 docker runを実行してコンテナを作成&起動します。-dはバックグラウンド実行(デーモン実行)を指定し、-pで「ホストのポート番号:コンテナのポート番号」を指定し、マッピングを行います。

    $ docker run -d -p 80:80 --name nginx10 kitano/nginx:1.0
    

     

  4. 実行確認

    curlコマンドでサイト確認を行ってみます。

    $ curl localhost
    sios
    

    用意したファイルが表示されているので、動作に問題はないようです。

  Dockerfileの主要な命令は次のとおりです。

命令機能
FROM ベースイメージの指定
MAINTAINER イメージの作成者
RUN コマンドの実行
CMD docker run時に実行されるコマンド
EXPOSE 外部に公開するポート
ENV 環境変数の設定
ADD 指定したディレクトリ/ファイルをコンテナ内にコピー
COPY 指定したディレクトリ/ファイルをコンテナ内にコピー
ENTRYPOINT docker run時に実行されるコマンド
VOLUME 外部からマウント可能なボリュームを指定
USER コンテナで使用されるユーザー名
WORKDIR 作業ディレクトリを指定
ONBUILD 次回ビルド時に実行されるコマンド

  CMDENTRYPOINTは、どちらも「docker run時に実行されるコマンド」です。これらのコマンドの違いは、CMDdocker runで実行するコマンドを上書きでき、ENTRYPOINTは上書きできないということです。

 また、Dockerfileの実行は1行ごとにシェルが起動するため、環境変数を引き継ぐことができません。そのため、環境変数の指定は、Dockerfile内にWORKDIRENVを利用して指定することをお勧めします。

  Dockerfileそのものは非常にシンプルな記法ですが、行き当たりばったりで構成していくと、可読性が低くなったり、効率の悪いシステムになってしまいがちです。公式サイトにDockerfileのベストプラクティスについて書いた記事がありますので、一度目を通しておくといいでしょう。

 なお、このサイトには「1つのコンテナでは1つのプロセスだけを動かす」(Run only one process per container)というベストプラクティスが紹介されているのですが、中にはどうしても1つのコンテナ上で複数プロセスを動かしたいという方もいるかもしれません。とはいえ、Dockerではフォアグラウンドで動かしているプロセスが終了するとコンテナも終了してしまうという事情もあるため、supervisorやmonitなどのサーバー監視ツールを利用したり、tail -f /dev/nullを実行して終了させないようにするなどの工夫を行っているようです。

複数コンテナの連携

 より実用的な例として、Ruby on Rails(RoR)サーバー用とDBサーバー用の2つのコンテナを用意して、それぞれを連携させてみましょう。ここでは、Redmineを動かしてみます。

    1. イメージのダウンロード

      RedmineとPostgreSQLのDockerイメージをダウンロードします。

      $ docker pull sameersbn/redmine:3.0.3
      $ docker pull sameersbn/postgresql:9.4
      
    2. PostgreSQL用コンテナの起動

      下記のコマンドラインでPostgreSQLのコンテナを起動します。

      $ docker run --name=postgresql-redmine -d \
        --env='DB_NAME=redmine_production' \
        --env='DB_USER=redmine' --env='DB_PASS=password' \
        --volume=/srv/docker/redmine/postgresql:/var/lib/postgresql \
        sameersbn/postgresql:9.4
      
    3. Redmine用コンテナの起動

      下記のコマンドラインでRedmineのコンテナを起動します。ポイントになるのは2行目の「--link=postgresql-redmine:postgresql」の部分で、このリンクを行うことで、PostgreSQL用コンテナとの接続情報が環境変数を介して共有され、コンテナ間の接続が可能になります。また、4行目の「--volume=/srv/docker/redmine/redmine:/home/redmine/data」では、コンテナが終了してもデータを永続化できるよう、ホストのディスク上にデータを保存しています。

      $ docker run --name=redmine -d \
        --link=postgresql-redmine:postgresql --publish=10083:80 \
        --env='REDMINE_PORT=10083' \
        --volume=/srv/docker/redmine/redmine:/home/redmine/data \
        sameersbn/redmine:3.0.3
      

 この2つのコンテナを起動して、ホストマシンのブラウザからhttp://localhost:10083で接続すると、Redmineのログイン画面が表示され、問題なく2つのコンテナが連携させることができました。

 

 2つのコンテナ上で動作しているRedmine

 これは、1つのホスト上に2つのコンテナを連携させるという例でした。では、複数のホスト上に複数のコンテナを動かす場合はどうすればいいでしょうか? Docker社は「Ambassador」という、「トラフィックを別ホストへforwardすることに特化したコンテナ」を用意するモデルを推奨しているようです。

(consumer) --> (redis-ambassador) ---network---> (redis-ambassador) --> (redis)

 ただし、さらに規模が大きくなって複数のホスト上に複数のコンテナを動かすようになると、目的のコンテナがどこのホスト上で動いているかを見つけるサービスディスカバリや異常終了を検知して再起動を行うフェイルオーバーなど、運用はより複雑となります。そうした問題への解となるべく注目されているのが、Google社が公開したオープンソースのコンテナクラスタ管理システム「Kubernetes」(略称はk8s)や、Docker社が発表した下記の3つの分散環境用オーケストレーションツールです。

  • Docker Machine(Dockerがインストールされたホストマシンを作成するもの)
  • Docker Swarm(Dockerクラスタリングのサポートツール)
  • Docker Compose(yml形式の設定ファイルで複数のDockerコンテナ環境を構築・操作。旧名Fig)

 これらのツールについても別の機会に調査したいと思います。

Dockerの使いどころ

 Dockerはどういった用途で利用するのが効果的でしょうか? 新しい環境をすぐに用意できることから、真っ先に思いつくのがCI(Continuous Integration:継続的インテグレーション)を始めとする開発環境での利用です。この点においてはVagrantと重なる部分もあります。

 ただし、冒頭でも述べたとおり、コンテナ型仮想化技術を利用していることによる軽量さが仮想マシン型のVagrantよりも秀でている点になります。逆に、Linux以外が絡む環境なら無条件にVagrantということになります。

 また、Vagrantとの比較で言えば、Vagrantがあくまでも開発環境での利用だけを想定しているのに対し、Dockerは本番環境での利用も想定している点が大きく異なります。Dockerの活用事例はこれからいろいろ広がってくると思いますが、現時点での活用事例を紹介しているサイトがありましたので、お知らせしておきます。

最後に……

 Vagrant、Dockerの流れで、「Packer」という単語もよく耳にします。名前からしてDockerと関係がありそうですが……。

  Packer
 
Packerは、仮想イメージ作成ツールです。Vagrantの作者であるMitchell Hashimoto氏によってGo言語で作成され、MPL2 Licenseで配布されているオープンソースソフトウェアです(ソースコードは、https://github.com/mitchellh/packerにあります)。

 どうやら、Dockerと特別な関係があるわけではなく、どちらかといえばVagrantのほうと関係が深いようです(作者が同じなので)。こちらも別の機会に調べてみたいと思います。

サイオスOSSよろず相談室

サイオスOSSよろず相談室(1)

問い合わせボタン