Edited at

Dockerを基本から理解する


はじめに

Docker を基本からちゃんと勉強したかったので、公式チュートリアル に取り組みながら詳しく理解したことを残していこうと思います。ドキュメントは英語で書いてありますが、簡単な英語を使っていて筆者程度の英語力でも普通に理解できました。(日本語化プロジェクトというがあるので、手っ取り早く読みたい人は こちら も参考にしてみてください。)


:raising_hand: 対象読者


  • Docker?とは?:thinking: の人

  • Docker使ってるけど概念とか仕組みをわからずにいる人

  • 環境構築でいつも苦戦する人(VirtualBox や Vagrantから卒業したい人)

  • コンテナ?何がおいしいの?が腑に落ちてない人


:notebook: Docker 公式チュートリアル 目次


  • Get Docker


    • Overview of Docker editions

    • Docker CE

    • Docker EE

    • Compability between Docker versions



  • Get started


    • Get started with Docker


      • Part1:Orientation

      • Part2:Containers

      • Part3:Services

      • Part4:Swarms

      • Part5:Stacks

      • Part6:Deploy your app <--- この辺までやります



    • Docker overview



  • Develop with Docker


    • Develop your app on Docker

    • Develop using the Docker Engine SDKs and API



  • Configure networking

  • Manage application data

......


:whale: Get Docker - Docker を手に入れる -


Overview of Docker editions - 概要と2つのエディション -

Docker はコミュニティ版(CE)とエンタープライズ版(EE)があります。 Docker CE は、ローカル環境でコンテナをベースとしたアプリケーションを実験するのに最適で、もう一方の Docker EE は、本番環境でスケールするようなビジネスにとって重要なアプリケーションを構築・移動・実行のために用意されています。


:whale: Get sarted - 始めよう -


Get started with Docker - 使い始める -


Part1 : Orientation - 概要説明とセットアップ -


コンテナと仮想環境

イメージ(image) とは、軽量で単独で動作するパッケージです。コード、ランタイム、ライブラリ、環境変数、設定ファイルなど、ソフトウェアの実行に必要な部品すべてを含んでいます。

コンテナ(container) とは、イメージのランタイム・インスタンスです。

また、コンテナはホスト上にあるハイパーバイザーを通してアクセスする仮想マシンと比較し、アプリケーションを直接実行するので性能が良く、各プロセスが分離しているので実行中のメモリのみ確保されます。


仮想マシン



コンテナ



セットアップ

まずは、Dockerを各環境にインストールします。筆者はMacユーザなので、同じ人はこちらの Docker Desktop for Mac からインストールできます。(それ以外の方は、各プラットフォームに応じたインストール先を参照して下さい。【Docker のインストール】

インストールすると、コンソールで docker コマンドが使えるようになっているか確認します。


Docker のバージョンを確認


console

$ docker --version

Docker version 18.09.2, build 1234567

$ docker info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 17.12.0-ce
Storage Driver: overlay2
...



イメージの起動

次のコマンドを実行すると、環境のセットアップを簡単に確認できます。

run コマンドでは、イメージからコンテナを起動します。この際にローカルにイメージがない場合は、 Docker Hub から自動的にインストールしてきます。


console

$ docker run hello-world

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
ca4f61b1923c: Pull complete
Digest: sha256:ca0eeb6fb05351dfc8759c20733c91def84cb8007aa89a5bf606bc8b315b9fc7
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.
...



:bulb:まとめ

コンテナ化は、複数のソフトウェアを全く気にせず、あたかも1つのソフトを使用しているかのような CI/CD を実現します。アプリケーションを拡張する際、重たいVMホストを動かすのと異なり、Docker を使うということは実行ファイルを動かすだけで良いのでとても便利:smirk:


Part2 : Containers - コンテナ -


Dockerfileでコンテナを定義

これまでだと、マシン上にソフトウェアをインストールするところから始めないといけませんが、Dockerは、 Dockerfile というものに移動可能なイメージを定義していきます。

ご自分の好きなディレクトを作成して、そこの中で作業を進めていきます。 $ cd /directory/what/you/like$ vim Dockerfile としてファイルに書いていきます。


Dockerfile

# 公式 Python ランタイムを親イメージとして使用

FROM python:2.7-slim

# 作業ディレクトリを /app に設定
WORKDIR /app

# 現在のディレクトリの内容を、コンテナ内の /app にコピー
COPY . /app

# requirements.txt で指定された必要なパッケージを全てインストール
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# ポート 80 番をコンテナの外の世界でも利用可能に
EXPOSE 80

# 環境変数を定義
ENV NAME World

# コンテナ起動時に app.py を実行
CMD ["python", "app.py"]


次に Dockerfile で参照している app.py , requirements.txt を作成します。


app.py

from flask import Flask

from redis import Redis, RedisError
import os
import socket

# Connect to Redis
redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2)

app = Flask(__name__)

@app.route("/")
def hello():
try:
visits = redis.incr("counter")
except RedisError:
visits = "<i>cannot connect to Redis, counter disabled</i>"

html = "<h3>Hello {name}!</h3>" \
"<b>Hostname:</b> {hostname}<br/>" \
"<b>Visits:</b> {visits}"
return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits)

if __name__ == "__main__":
app.run(host='0.0.0.0', port=80)



requirements.txt

Flask

Redis


アプリケーションの構築

ディレクトリのトップが以下のようになっているのを確認して、アプリケーションを構築していきます。まず build コマンドでイメージを作成します。(この際につける --tag オプションは -t で省略可)


console

$ ls

Dockerfile app.py requirements.txt

$ docker build --tag=friendlyhello .
Sending build context to Docker daemon 5.12kB
Step 1/7 : FROM python:2.7-slim
...
Successfully built 8f74a6392754
Successfully tagged friendlyhello:latest


マシン上のローカルにある Docker イメージ・レジストリの中にイメージが作成されます。


console

$ docker image ls

REPOSITORY TAG IMAGE ID
friendlyhello latest 326387cea398



アプリケーションの実行


console

$ docker run -p 4000:80 friendlyhello


-p オプションでは、ポートの割り当てを指定します。今回は、ポート 4000 でアクセスすると、Dockerfile で定義した公開ポートを定義している EXPOSE の80番に割り当てています。localhost:4000 にブラウザでアクセスすると、以下のように表示できていれば無事にコンテナが起動できています。( Hostaname にはコンテナID が表示されています。)

また、 curl コマンドをシェルで実行するとコンテンツが表示されることでも確認できるでしょう。


console

$ curl http://localhost:4000/

<h3>Hello World!</h3><b>Hostname:</b> 0c560f01cca3<br/><b>Visits:</b> <i>cannot connect to Redis, counter disabled</i>

以下のように -d オプションを付与して実行すると、デタッチド・モードでコンテナの起動ができます。


console

$ docker run -d -p 4000:80 friendlyhello

0c15b3a914c4fd4675eccab0758415f47f3f01ce90680ba541e996e2418adb7e

コンテナがバックグラウンドで実行しているか確認します。また、実行中のコンテナを止める方法としても次のようにできます。(コマンドでコンテナIDを指定する際は短縮系も使うことができます。)


console

$ docker container ls

CONTAINER ID IMAGE COMMAND CREATED STATUS
0c15b3a914c4 friendlyhello "python app.py" 4 seconds ago Up 3 seconds

$ docker container stop 0c15b3a914c4
0c15b3a914c4



イメージの共有

:bell:おことわり

dockerアカウントもお持ちでない方は、こちらの DockerHub への登録を事前に済ませておく必要があります。

DockerHub とは、コンテナを共有・公開できるサービスです。GitHubのコンテナ版だと思って大丈夫です。リポジトリとして管理したり、タグをつけてバージョン管理したりとか非常に似ています。アカウントを作成したら早速公開のための準備をしていきます。


console

# 自分のローカルマシンから公開レジストリにログイン

$ docker login

# イメージのタグ付け ( $ docker tag username/repogitory:tag )
$ docker tag hoge/get-started:part2

# タグ付けしたイメージをリポジトリにアップ ( $ docker push username/repository:tag )
$ docker push hoge/get-started:part2


これでイメージの公開ができました。試しにDockerHubのマイページに行くと、公開できているのを確認できるかと思います。また、リモートリポジトリにイメージをアップしたことで、あらゆるマシンからイメージを取得することができるようになりました。


:bulb:まとめ

Dockerfile を書いていくところから、コンテナの起動や停止などの操作をしていきました。この辺は、すごく基本的なことなのでそれぞれのコマンドが何をしているのかまで理解しておきたいです!


Part3: Services - サービス -

Part3では、アプリケーションをスケールアウトして負荷分散を有効にします。そこで大切な概念として サービス というものがあります。サービスとはまさに本番環境におけるコンテナ化ということでしょう…とか書いてありますが、なんのこっちゃという感じなのでとにかく進めていきます。

Docker でのサービスの定義、実行、スケールは docker-composer.yml でできます。それでは、ファイルを作成して以下のように書いていきます。


docker-compose.yml


version: "3"
services:
web:
# 自分用に置き換える
image: username/repo:tag
deploy:
replicas: 5
resources:
limits:
cpus: "0.1"
memory: 50M
restart_policy:
condition: on-failure
ports:
- "4000:80"
networks:
- webnet
networks:
webnet:

次に $ docker stack deploy コマンドを実行しますが、エラーになるのでまず先に Swarm クラスタを初期化しておく必要があります。( ※ Swarm については Part4 で詳しく触れます)


console

$ docker swarm init

$ docker stack deploy -c docker-compose.yml getstartedlab # ここでは 「getstartedlab」 とアプリに名前を設定して実行します。
Creating network getstartedlab_webnet
Creating service getstartedlab_web

$ docker service ls # アプリに関係のある実行中のサービスを確認
ID NAME MODE REPLICAS IMAGE PORTS
<hash> getstartedlab_web replicated 5/5 yoshimitsu4432/get-started:part2 *:4000->80/tcp

$ docker service ps <hash> # アプリに関係のあるタスクの一覧を確認

ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
jqur66acky0d getstartedlab_web.1 username/repo:tag linuxkit-025000000001 Running Running 22 hours ago
jqurdafj1kn1 getstartedlab_web.2 username/repo:tag linuxkit-025000000001 Running Running 22 hours ago
dfb209ufbslk getstartedlab_web.3 username/repo:tag linuxkit-025000000001 Running Running 22 hours ago
g309bsldhgou getstartedlab_web.4 username/repo:tag linuxkit-025000000001 Running Running 22 hours ago
kpfsi29nl492 getstartedlab_web.5 username/repo:tag linuxkit-025000000001 Running Running 22 hours ago


5つのタスクが各々の状態と ID を持つのがわかります。 http://locahost:4000 で開いている画面を何度かリロードしてみると、アクセスごとにコンテナID が変わることがわかるかと思います。つまり、5つレプリカのうち1つが自動で選ばれて負荷分散しているのが確認できるかと思います。(レプリカは docker-compose.ymlreplicas を変更し、 $ docker stack deploy を再度実行することでスケールできます)


アプリと swarm の解体


console

$ docker stack rm getstartedlab     # アプリケーションを削除

Removing service getstartedlab_web
Removing network getstartedlab_webnet

$ docker node ls # swarmのノードが起動・実行中か確認
ID HOSTNAME STATUS
gjfa23ij08fka92ks linux-kit-0250000001 Ready

# swarmクラスタを停止
$ docker swarm leave --force
Node left the swarm.



:bulb:まとめ

docker-compose.yml でコンテナの挙動をコード化し、サービスとして定義しました。また $ docker stack deploy コマンドを実行するだけで、起動中のサービスをスケール・制限・デプロイすることができます。


Part4: Swarms


Swarm クラスタの理解

Part3 までは1つのホストにコンテナを配置することを考えてきましたが、多くのリクエストを裁く必要のあるシステムでは複数のホストに複数のコンテナを配置させておく必要があります。Docker Swarm は Dokcer のホストを束ねてクラスタ化するためのツールです。これから現在使っているマシンを Swarm マネージャとし、仮想マシンで作成し、swarmに追加していきます。


swarm のセットアップ

仮想マシンを作成できるハイパーバイザーとしては、VirtualBoxを使います。

こちらから VirtualBox をインストールできます

次に docker-machine を使って2つの仮想マシンを作っていきます。(ローカル環境が)


クラスタの作成


console

$ docker-machine crearte --driver Virtualbox myvm1

$ docker-machine crearte --driver Virtualbox myvm2

$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
myvm1 - virtualbox Running tcp://192.168.99.100:2376 v18.09.6
myvm2 - virtualbox Running tcp://192.168.99.101:2376 v18.09.6


myvm1myvm2 の2つの仮想マシンを作成し、それらを表示させました。1つをマネージャ、もう1つをワーカに設定していきます。


console

$ docker-machine ssh myvm1 "docker swarm init --advertise-addr 192.168.99.100:2376"

Swarm initialized: current node (<-- node ID -->) is now a manager.
...

仮想マシンには docker-machine ssh <your machine name> でコマンドを送ることができます。無事に応答が帰ってきたら、myvm1 をマネージャにすることができているので、 次に myvm2 をワーカとして追加していきます。


console

$ docker-machine ssh myvm2 "docker swarm join --token <token> <ip>:<port>"   # <port> については 2377 を指定します。

This node joined a swarm as a worker.

これでクラスタの作成が完了しました。

次のように $ docker-machine ssh <your machine name> コマンドで直接ログインもできます。(出たい時は exit


console

$ docker-machine ssh myvm1

( '>')
/) TC (\ Core is distributed with ABSOLUTELY NO WARRANTY.
(/-_--_-\) www.tinycorelinux.net

docker@myvm1:~$ # swarm のノード一覧を表示させてみましょう

docker@myvm1:~$ docker node ls # docker-machine ssh myvm1 "docker node ls" と同じ
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
<-- myvm1 node ID --> * myvm1 Ready Active Leader
<-- myvm2 node ID --> myvm2 Ready Active

docker@myvm1:~$ exit
logout



アプリをクラスタ上にデプロイ

$ docker-machine scp コマンドを使ってローカルのファイルを myvm1 へ送信します。


console

$ docker-machine scp docker-compose.yml myvm1:~    # myvm1 のホームディレクトリ(~)へコピー


それでは、 myvm1 に swarm マネージャを使ってデプロイしていきましょう。と言ってもとても簡単で part3 のコマンドを myvm1 に送ってあげるだけです!


console

$ docker-machine ssh myvm1 "docker stack deploy -c docker-compose.yml getstartedlab"

$ docker-machine ssh myvm1 "docker stack ps getstartedlab"

ID NAME IMAGE NODE DESIRED STATE
jq2g3qp8nzwx test_web.1 username/repo:tag myvm1 Running
88wgshobzoxl test_web.2 username/repo:tag myvm2 Running
vbb1qbkb0o2z test_web.3 username/repo:tag myvm2 Running
ghii74p9budx test_web.4 username/repo:tag myvm1 Running
0prmarhavs87 test_web.5 username/repo:tag myvm2 Running


NODE を見てみるとちゃんと myvm1, 2 に分散できているのが分かります。仮想マシンのIPアドレスでアクセスができます。以下の画像のように作成されたネットワークがホストで共有され、負荷分散されます。




クリーンアップ

クラスタの解体は docker stack rm <your machine name> コマンドで解体ができます。


console

$ docker-machine ssh myvm1 "docker stack rm getstartedlab"

$ # 今回はそのままにしておきますが、ワーカの削除も以下の要領でできます。
$ docker-machine ssh myvm2 "docker swarm leave" # ワーカの削除
$ docker-machine ssh myvm1 "docker swarm leave --force" # マネージャの削除( -f オプション )



:bulb:まとめ

part4では、swarm のセットアップ方法を学びました。swarm とは、Docker を実行しているマシンのクラスタであり、アプリケーションを複数のマシンへデプロイできます。


Part5: Stacks - スタック -


新しいサービスの追加と再デプロイ

スタックは、相互関係を持つサービスのグループで、オーケストレートやスケールします。スタックで性能を定義して、マシンをコード化していきます。ちなみに、ここでは、自由に使える可視化サービス visualizer を追加しています。


docker-compose.yml

version: "3"

services:
web:
# 自分の名前にあわせて書き換える
image: username/repo:tag
deploy:
replicas: 5
restart_policy:
condition: on-failure
resources:
limits:
cpus: "0.1"
memory: 50M
ports:
- "80:80"
networks:
- webnet
visualizer:
image: dockersamples/visualizer:stable
ports:
- "8080:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
deploy:
placement:
constraints: [node.role == manager]
networks:
- webnet
networks:
webnet:

visualizer では、 Docker ホスト側のソケットファイルにアクセスするための volumes を、サービスが swarm マネージャのみでしか動作しないよう placement で指定しています。


console

$ docker-machine scp docker-compose.yml myvm1:~

docker-compose.yml 100% 630 1.6MB/s 00:00

$ docker-machine ssh myvm1 "docker stack deploy -c docker-compose.yml getstartedlab"
Creating service getstartedlab_visualizer
Updating service getstartedlab_web (id: m1arwuh9q63ztwjlg9yauw961)

$ docker-machine ssh myvm1 "docker stack ps getstartedlab"



console

$ docker-machine ssh myvm1 "docker stack ps getstartedlab"



データの保持

次い、アプリのデータを保管する Redis データベースを追加します。


docker-compose.yml

version: "3"

services:
web:
# 自分の名前とイメージに合わせる
image: username/repo:tag
deploy:
replicas: 5
restart_policy:
condition: on-failure
resources:
limits:
cpus: "0.1"
memory: 50M
ports:
- "80:80"
networks:
- webnet
visualizer:
image: dockersamples/visualizer:stable
ports:
- "8080:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
deploy:
placement:
constraints: [node.role == manager]
networks:
- webnet
redis:
image: redis
ports:
- "6379:6379"
volumes:
- ./data:/data
deploy:
placement:
constraints: [node.role == manager]
networks:
- webnet
networks:
webnet:


console

$ docker-machine ssh myvm1 "mkdir ./data"

$ docker-machine scp docker-compose.yml myvm1:~
docker-compose.yml 100% 630 1.6MB/s 00:00

$ docker-machine ssh myvm1 "docker stack deploy -c docker-compose.yml getstartedlab"
Creating service getstartedlab_visualizer
Updating service getstartedlab_web (id: hogehoge1234)


この part で出てきたビジュアライザーがブラウザで確認できませんでした:scream:

ドキュメント上では、 8080 にアクセスすると表示できるとは書いてありました。


ビジュアライザはスタンドアローンのサービスのため、スタック上のあらゆるサービスと実行できます。


もしご知見ある方がいらっしゃればご教授いただけると助かります。


:bulb:まとめ

内部で連携するサービスが全て動作するというスタックについて学びました。スタックへのサービスの追加は Compose ファイルへ書き込み、場所の制約とボリュームを利用することでコンテナを停止したり、再デプロイしたりしてもデータを保持し続けることができることが分かりました。


Part6: Deploy your app


Docker Cloud へ接続

以下のプロバイダなどを使用して、Docker Cloud へ接続することができます。


  • Amazon Web Serevice

  • DigitalOcean

  • Microsoft Azure

そして、Docker Cloud を通して、アプリのデプロイをすることでアプリケーションの起動を開始できます。

これで Docker プラットフォーム全体としてのフルスタック、すなわち開発からデプロイへの流れを習得しました。


おわりに

Docker について基本的なイメージやコンテナ、サービスなどの概念から swarm、スタック、スケーリング、負荷分散などの考え方を学ぶことができたかなと思います。もう少し深く学んでいきたいので、これからもうちょっと踏み入れて見ようかと思います。


参考