【Live版Ubuntuへ移行】Dockerを公式チュートリアル”Get Started with Docker”で学ぶ【中編】

一連の流れのある【中編】になります。 ここまできてようやくチュートリアルのPart3が進められるような状況になりました。今回からはOSをChromeOSからUbuntu 18.02 LTSに変更して行っていきます。

【参考】

uepon.hatenadiary.com

uepon.hatenadiary.com

uepon.hatenadiary.com

以前のエントリの内容と重複はありますが、チュートリアルを進めていきます。 ここまで飛ばしていいという部分には【ここまで読み飛ばしていいです】という目印をつけておこうと思います。

Get Started, Part 3: Services

ここではServiceに関して学ぶことになります。

docs.docker.com

事前準備

事前準備としてはDockerのインストールは当然のことながらPart1とPart2を行ってきた環境(コンテナ)があることが必要です。

加えてDocker Composeのインストールが必要になります。Mac用のDockerデスクトップとWindows用のDockerデスクトップにはプレインストールされていますが、Linux系は別途インストールする必要があります。以下のサイトを参考にインストールを行います。

docs.docker.com

$ sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose

また、Part2で作成したfriendlyhelloイメージをレジストリにpushして公開することが必須になります。(ここではその共有イメージを使います。) 以下のようにDockerのイメージを実行し、http://localhost:8080/という感じでアクセスできることは確認しておいてください

※原文は4000ポートを使用していますがChromeOSではアクセスがNGなので8080に変更してありますので注意です。

【一般形】

$ docker run -p 8080:80 username/repo:tag

【前回の例で以下のように動作していました(ここでいうhogeは各自のユーザ名に変更してください)】

$ docker run -p 8080:80 hoge/get-started:part2

サービスとは?

このPartではアプリケーションを拡張し負荷分散を行います。そのために分散アプリケーションの階層を1レベル上に上げ、サービスという概念が必要になります。

階層的にはこんな感じ

  • Stack
  • Services (←いまここ)
  • Container (part2(前回)でやった部分)

分散アプリケーションはサービスという概念で構成されていて、例えばビデオ共有サイトでは、データベースサービス、ビデオをトランスコーディングするサービス、フロントエンド用のサービスから構成されているようなイメージになります。

サービスはコンテナから構成されます。その設定でイメージの実行方法、使用するポート、コンテナのレプリカをいくつ実行する必要があるかなどの体系化を行う必要があります。これらの設定を行う場合にはdocker-compose.ymlを記述することでこれらの定義、実行、拡張することが可能になります。

docker-compose.ymlを書いてみる

docker-compose.ymlでコンテナの動作を設定してみます。

以下に記載してymlファイルdocker-compose.ymlとして作成します。 このとき、Part2で作成したイメージをレジストリにpushしていることを確認して、自分の作成した username/repo:tagをイメージの詳細に置き換えてこの.ymlを更新します。 以下の例ではhoge/get-started:part2として設定しています。またポートも4000から8080へ変更しています。

【変更前(原文のまま)】

version: "3"
services:
  web:
    # replace username/repo:tag with your name and image details
    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:

【今回の変更後】

version: "3"
services:
  web:
    # replace username/repo:tag with your name and image details
    image: hoge/get-started:part2
    deploy:
      replicas: 5
      resources:
        limits:
          cpus: "0.1"
          memory: 50M
      restart_policy:
        condition: on-failure
    ports:
      - "8080:80"
    networks:
      - webnet
networks:
  webnet:

このdocker-compose.ymlでは次のような動きを記述しています。

  1. Part2で作成したイメージをリポジトリから取得。
  2. 取得したイメージから5つのインスタンスを生成して、webと呼ばれるサービスとして名づけます。それぞれに対してCPU資源は10%、50MBのRAMを割当て。
  3. コンテナでエラーがあった場合には再起動。
  4. webサービスの各インスタンスの80ポートはローカルホストからは8080ポート経由からアクセス可能。(オリジナルの記述は4000ポート)
  5. webnetと呼ばれる負荷分散ネットワークを生成し、ポート80を共有するようにwebのコンテナに指示します。 (内部的にはコンテナ自体が一時ポートでwebのポート80に発行)
  6. webnetネットワークをデフォルト設定(負荷分散オーバーレイネットワーク)で定義。

【ここまで読み飛ばしていいです】

負荷分散ネットワークアプリの起動

まずは以下のコマンドを起動します。コマンドの意味はPart4で説明がありますが、これを実行しないとエラーが発生します。

$ docker swarm init

【ログ】

$ docker swarm init
Swarm initialized: current node (nc03uv6m1z1dxgwxdilvh29rj) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-1dukwcs4w07qt835cw6o5w0aju191e93be2jtctejrndzj5jyd-25d5b683d7jx4hceptehbtrz9 192.168.0.7:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

つづいて以下のように実行します。この実行ではアプリはgetstartedlabと設定しています。

$ docker stack deploy -c docker-compose.yml getstartedlab

【ログ】

$ docker stack deploy -c docker-compose.yml getstartedlab
Creating network getstartedlab_webnet
Creating service getstartedlab_web

これで動作の確認になります。ここからはChromeOSではうまく動作しなかった部分になりますが… 以下のコマンドで確認します。

$ docker service ls

【ログ】

$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE                      PORTS
p1dyl3harlzm        getstartedlab_web   replicated          0/5                 hoge/get-started:part2   *:8080->80/tcp

お!あれ?だめなのか?

念の為、コンテナの動作状況を以下のコマンドで確認してみます。

$ docker service ps getstartedlab_web

【ログ】

$ docker service ps getstartedlab_web 
ID                  NAME                  IMAGE                      NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
lgz0af823dtz        getstartedlab_web.1   hoge/get-started:part2   ubuntu              Running             Running 50 seconds ago                       
n2bgt0wx11kg        getstartedlab_web.2   hoge/get-started:part2   ubuntu              Running             Running 22 seconds ago                       
0b1v6d8jy5lu        getstartedlab_web.3   hoge/get-started:part2   ubuntu              Running             Running 50 seconds ago                       
sm21dfefpfgw        getstartedlab_web.4   hoge/get-started:part2   ubuntu              Running             Running 32 seconds ago                       
isx43duompwd        getstartedlab_web.5   hoge/get-started:part2   ubuntu              Running             Running 11 seconds ago 

ステータスはRunningになっています!動作しているようです。念の為もう一度Serviceの動作状況の確認を調べてみると…

$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE                      PORTS
p1dyl3harlzm        getstartedlab_web   replicated          5/5                 hoge/get-started:part2   *:8080->80/tcp

無事に起動しているようです。ちょっとホッとしました。

今回の動作環境ではDockerでaufsを使用していないため、パフォーマンスが劇的に低くなっているため各コンテナの起動に時間がかかってしまったのではないかと考えます。(他の例でも、起動に関するパフォーマンスは明らかに落ちているのはわかります。)

ではブラウザでこの負荷分散サービスへアクセスしていきます。 アクセス先は同様にhttp://localhost:8080/になります。このURLにアクセスするとロードバランスを図ってくれるのでアクセスする度(リロードする度)に 表示されるHostIDが変化していくことを確認してください。

f:id:ueponx:20190322062553p:plain

f:id:ueponx:20190322062602p:plain

f:id:ueponx:20190322062611p:plain

$ docker swarm init
Swarm initialized: current node (nc03uv6m1z1dxgwxdilvh29rj) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-1dukwcs4w07qt835cw6o5w0aju191e93be2jtctejrndzj5jyd-25d5b683d7jx4hceptehbtrz9 192.168.0.7:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

$ docker stack deploy -c docker-compose.yml getstartedlab
Creating network getstartedlab_webnet
Creating service getstartedlab_web

$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE                      PORTS
p1dyl3harlzm        getstartedlab_web   replicated          0/5                 hoge/get-started:part2   *:8080->80/tcp

$ docker service ps getstartedlab_web 
ID                  NAME                  IMAGE                      NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
lgz0af823dtz        getstartedlab_web.1   hoge/get-started:part2   ubuntu              Running             Running 50 seconds ago                       
n2bgt0wx11kg        getstartedlab_web.2   hoge/get-started:part2   ubuntu              Running             Running 22 seconds ago                       
0b1v6d8jy5lu        getstartedlab_web.3   hoge/get-started:part2   ubuntu              Running             Running 50 seconds ago                       
sm21dfefpfgw        getstartedlab_web.4   hoge/get-started:part2   ubuntu              Running             Running 32 seconds ago                       
isx43duompwd        getstartedlab_web.5   hoge/get-started:part2   ubuntu              Running             Running 11 seconds ago                       

$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE                      PORTS
p1dyl3harlzm        getstartedlab_web   replicated          5/5                 hoge/get-started:part2   *:8080->80/tcp

f:id:ueponx:20190324071640p:plain


アプリケーションのスケールをしてみる

スケールをするのは簡単です。 docker-compose.ymlのレプリカの数を変更して保存し、再度docker stack deployコマンドを再実行するだけでアプリを拡張できます。 以下の例ではレプリカ数を5から3へ変更しています。非常にシームレスにスケールするのがわかります。

$ vim docker-compose.yml

$ docker stack deploy -c docker-compose.yml getstartedlab
Updating service getstartedlab_web (id: p1dyl3harlzm5ghawpj04noay)

$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE                      PORTS
p1dyl3harlzm        getstartedlab_web   replicated          3/3                 hoge/get-started:part2   *:8080->80/tcp

$ docker service ps getstartedlab_web 
ID                  NAME                      IMAGE                      NODE                DESIRED STATE       CURRENT STATE             ERROR               PORTS
7qeqrcn6etna        getstartedlab_web.1       hoge/get-started:part2   ubuntu              Running             Running 20 seconds ago                        
lgz0af823dtz         \_ getstartedlab_web.1   hoge/get-started:part2   ubuntu              Shutdown            Shutdown 24 seconds ago                       
y52g099pbi4f        getstartedlab_web.2       hoge/get-started:part2   ubuntu              Running             Starting 2 seconds ago                        
n2bgt0wx11kg         \_ getstartedlab_web.2   hoge/get-started:part2   ubuntu              Shutdown            Shutdown 2 seconds ago                        
0b1v6d8jy5lu        getstartedlab_web.3       hoge/get-started:part2   ubuntu              Running             Running 10 minutes ago

負荷分散させるアプリケーションがdocker-comporse.ymlの変更を行ってコマンド一発でスケールできるのは非常に便利です!

後片付け

Serviceの終了を指せる場合には以下のコマンドを使用します。

$ docker stack rm getstartedlab
$ docker swarm leave --force

これで今回の環境は完全に終了します。

【ログ】

$ docker stack rm getstartedlab
Removing service getstartedlab_web
Removing network getstartedlab_webnet

$ docker swarm leave --force
Node left the swarm.

f:id:ueponx:20190322062709p:plain

これでPart3は終了です。

Get Started, Part 4: Swarms

ここからはPart4でSwarmに関する部分になります。(Part3でもやってましたけど) Part4では、Swarmとはどういうものか、Swarm内のノードがマネージャまたはワーカーになる方法、Swarmを作成、その上にアプリをデプロイを行っていきます。 また、その環境下でPart3同様に異なるマシンで構成でDockerの基本機能、ネットワーク機能、負荷分散やスケールできることを実験してみます。

docs.docker.com

前提としてはPart3までが完了していればOKということになるのですが、新たにdocker-machineコマンドが必要になります。 WindowsOSXであればDocker環境をインストールするとすでに入るのですが、Linuxの場合には別途インストールが必要になります。 以下のサイトを見ながら作業となります。

docs.docker.com

抜粋すると以下のコマンドになります。(versionは0.16.0の場合)

f:id:ueponx:20190327055356p:plain

$ base=https://github.com/docker/machine/releases/download/v0.16.0 && curl -L $base/docker-machine-$(uname -s)-$(uname -m) >/tmp/docker-machine && sudo install /tmp/docker-machine /usr/local/bin/docker-machine

インストール後の動作チェックは以下で大丈夫です。

$ docker-machine version
docker-machine version 0.16.0, build 702c267f

f:id:ueponx:20190327055412p:plain

クラスタの作成

ドキュメントを読んでいくと…

You need a hypervisor that can create virtual machines (VMs), so install Oracle VirtualBox for your machine’s OS.

なんとvirtualboxをインストールしてVMを起動しろと言われます。(ChromeOSではここまで来ると本末転用のような状況…OS環境を変えて正解だったのかも) 仕方ないので、以下のリンクからUbuntu用のUbuntu 18.04 (Bionic) / Ubuntu 18.10 (Cosmic) / Debian Unstableパッケージをダウンロードします。

www.oracle.com

Ubuntuの場合、ブラウザ(Firefox)でダウンロードすると.debファイルを認識してパッケージのインストールを行ってくれますので、 そのままインストールしてしまいましょう。

インストールが完了したら、以下のように2台のVMを起動します。--driverで指定したVM環境が使用されVMが作成されます。

$ docker-machine create --driver virtualbox myvm1
$ docker-machine create --driver virtualbox myvm2

f:id:ueponx:20190327055425p:plain

f:id:ueponx:20190327055439p:plain

起動したVMmyvm1myvm2という名前で起動しています。このVMIPアドレスは以下のコマンドで調べることができます。

$ docker-machine ls
ubuntu@ubuntu:~$ docker-machine ls
NAME    ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER     ERRORS
myvm1   -        virtualbox   Running   tcp://192.168.99.101:2376           v18.09.3   
myvm2   -        virtualbox   Running   tcp://192.168.99.102:2376           v18.09.3   

f:id:ueponx:20190327055451p:plain

VMIPアドレスがわかったら、2つともIPアドレスを控えておきます。そのうちの1台(今回はmyvm1側)をSwarmのマネージャホストにします。 該当するホストでdocker-machine ssh経由でdocker swarm initコマンドを実行します。実行時に表示されるtokenはメモしておきます。というかコマンドそのものをコピーしておいたほうがいいかもしれません。(docker swarm join ほげほげの部分)

$ docker-machine ssh myvm1 "docker swarm init --advertise-addr <myvm1 ip>"
Swarm initialized: current node <node ID> is now a manager.

To add a worker to this swarm, run the following command:

  docker swarm join \
  --token <token> \
  <myvm ip>:<port>

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

f:id:ueponx:20190327055503p:plain

【ログ】

$ docker-machine ssh myvm1 "docker swarm init --advertise-addr 192.168.99.101"
Swarm initialized: current node (76irf1ec7b14xfns1oym7638x) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-0hdt9lh014fow8y0yrzw6sruqdzb275sw2ohwhiolzfy0ev05z-42bhd4wxtwd9hu7ndx9prrzzd 192.168.99.101:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

念の為、Swarmを使用する場合にはポート2377とポート2376を使用する可能性があるので、使用しないように注意が必要です。 また、上記のdocker-machine sshでコマンドを送信できない場合にはホストにネイティブにインストールされているsshコマンドを使用する事もできます その場合には--native-ssh sshオプションを追加設定します。具体的には以下のようになります。

$ docker-machine --native-ssh ssh myvm1  "docker swarm init --advertise-addr <myvm1 ip>"

続いて2台目のVM(今回はmyvm2)をワーカーとして登録します。その際は先程実行したdocker swarm initの戻りでtokenが表示されているので それをオプション指定してdocker swarm joinをホストに送信します。例のは適切なものに変更を行ってください。

$ docker-machine ssh myvm2 "docker swarm join --token <token> <ip>:2377"

f:id:ueponx:20190327055520p:plain

$ docker-machine ssh myvm2 "docker swarm join --token SWMTKN-1-0hdt9lh014fow8y0yrzw6sruqdzb275sw2ohwhiolzfy0ev05z-42bhd4wxtwd9hu7ndx9prrzzd 192.168.99.101:2377"
This node joined a swarm as a worker.

これでSwarmが生成できました。以下のコマンドで生成されたノードを表示してみます。

$ docker-machine ssh myvm1 "docker node ls"

f:id:ueponx:20190327055530p:plain

ここでもし最初からやり直す場合にはdocker swarm leaveを実行するとSwarmが終了し元に戻ります。

アプリのデプロイを行う

Swarmができあがったので、アプリをデプロイしていきます。

これまではdocker-machine sshで実行していましたが、これではいちいちコマンドを実行していく必要があるので結構手間です。そこでdocker-machine envで得られる情報を元にローカルのdocker-compose.ymlファイルを使用してデプロイする方法もあります。今回はそれを使用してみます。

環境変数の設定はdocker-machine envで取得できるので

$ docker-machine env myvm1
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.101:2376"
export DOCKER_CERT_PATH="/home/ubuntu/.docker/machine/machines/myvm1"
export DOCKER_MACHINE_NAME="myvm1"
# Run this command to configure your shell: 
# eval $(docker-machine env myvm1)

この情報をevalで取り込んで有効化します。

$ eval $(docker-machine env myvm1)

f:id:ueponx:20190327055542p:plain

すると、今回作成したmyvm1がアクティブ状態になります。

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

f:id:ueponx:20190327055552p:plain

Part3と同様にアプリのデプロイして、動作を確認してみます。

【注意】デプロイを行う前にdocker-compose.ymlファイルのあるパスに移動しておいてください。 myvm1のホスト上で以下のコマンドを実行すると

$ cd ./friendlyhello/
$ docker stack deploy -c docker-compose.yml getstartedlab

【ログ】

$ docker stack deploy -c docker-compose.yml getstartedlab
Creating network getstartedlab_webnet
Creating service getstartedlab_web

myvm1myvm2にアプリが分散配置されて起動されます。以下のコマンドで分散の状況を確認することができます。

$ docker stack ps getstartedlab

f:id:ueponx:20190327062929p:plain

$ docker stack ps getstartedlab
ID                  NAME                  IMAGE                      NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
886onhvdec3j        getstartedlab_web.1   ueponx/get-started:part2   myvm2               Running             Running 3 minutes ago                        
ukvh7zbk85e8        getstartedlab_web.2   ueponx/get-started:part2   myvm1               Running             Running 3 minutes ago                        
tgvo877hsaox        getstartedlab_web.3   ueponx/get-started:part2   myvm2               Running             Running 3 minutes ago                        
ffzmzokyv6v7        getstartedlab_web.4   ueponx/get-started:part2   myvm1               Running             Running 3 seconds ago                       
jq5q4ukq9w6b        getstartedlab_web.5   ueponx/get-started:part2   myvm1               Running             Running 3 seconds ago

ここまできたらブラウザなどを使ってmyvm1またはmyvm2IPアドレスにアクセスを行います。

【myvm1へのアクセス】

f:id:ueponx:20190327055657p:plain

f:id:ueponx:20190327055708p:plain

f:id:ueponx:20190327055721p:plain

【myvm2へのアクセス】

f:id:ueponx:20190327055612p:plain

f:id:ueponx:20190327055623p:plain

f:id:ueponx:20190327055633p:plain

f:id:ueponx:20190327055646p:plain

この環境でスケールさせる場合にはdocker-compose.ymlを修正し、docker stack deployを実行することでスケールが用意にできます。またノードの追加に関しても今回のmyvm2と同様に追加することでホスト単位のスケールも用意にできるようになります。

stackの終了方法

stackの終了方法は今回の場合は以下のようになります。

$ docker stack rm getstartedlab

環境変数のクリア

docker-machine envで設定した設定は以下のように行うとクリアできます。

$ eval $(docker-machine env -u)

ローカルホストを終了させればdockerのマシンも終了します。状態を確認する場合には以下のコマンドとなります。

$ docker-machine ls

停止したノードを再起動させる場合にはホスト名を指定して以下のように再起動ができます。

$ docker-machine start <machine-name>

おわりに

OSを変更することでようやくPart3とPart4がおわらせられました。しかし、このチュートリアルかなり事前準備の要求が高いですね。あともう少し頑張ってみます。

【参考エントリー】

uepon.hatenadiary.com

uepon.hatenadiary.com

uepon.hatenadiary.com

Ubuntu/DebianのUSB起動のLIVE版でDockerを動作させる

前回の挫折を受けてChromeOSからUbuntuなどへの移行を考え、折角インストールしたChromeOSを消すのも、もったいないのでUSB版のLive環境をつかって継続してDockerを学ぶように方針変更をしたらまたトラブルが…という内容です。キッカケになった挫折に関しては以下参照で。

【参考】

uepon.hatenadiary.com

CentOSでも良かったのですが、USBメモリへの変更設定の保存などはできなかったので、今回はUbuntu18.02 LTSを使用することにしました。Debian系のLinuxはLive版でも設定やファイルをUSBメモリに保存できるのがいいですよね。ということでUbuntuの起動可能なメディアを作成します。ISOイメージがあれば以下のようなツールを使用することでUSBメモリ起動メディアが作成できます。

このツールを使用すると良いかなと思います。(ただし、イメージのダウンロードに関してはこのツール経由で行わない方が高速にできます)

unetbootin.github.io

作成したUSBメモリからPCを起動したら、ネットワークやキーボードマップなど設定を行いそのあとにDockerをインストールしていきます。公式ドキュメントもしっかりしているのでこちらを参考にしてもらえればと思います。

【公式ドキュメント】

docs.docker.com

コマンドだけをまとめると以下のような感じになります。

$ sudo apt-get remove docker docker-engine docker.io containerd runc
$ sudo apt-get update
$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo apt-key fingerprint 0EBFCD88
$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io
$ sudo docker run hello-world

これで完了するはずなのですが…また罠発生。インストール確認用の最後のテストコマンドであるsudo docker run hello-worldを実行してみると

$ sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
1b930d010525: Pull complete 
Digest: sha256:2557e3c07ed1e38f26e389462d03ed943586f744621577a99efb77324b0fe535
Status: Downloaded newer image for hello-world:latest
docker: Error response from daemon: error creating aufs mount to /var/lib/docker/aufs/mnt/b329231382bf8b24662da048500b454af804796ecdf11d92002f97186141984e-init: invalid argument.
See 'docker run --help'.

docker: Errorが発生します。このエラーはUbuntu 16.02 LTSDebian(最新)でも同じ状況になるようです。ググってもあまり情報がないことをみるとどうもUSB版のLive環境に固有の状況の模様。

エラーの内容からするとaufsというファイルシステムにコンテナイメージが生成できないよっていうエラーな感じです。

Wikipediaによれば

ja.wikipedia.org

aufs (AnotherUnionFS) は Linuxファイルシステムサービスであり、複数の異なるファイルシステム (ブランチと呼ばれる) のファイルやディレクトリ同士を透過的に重ねる (マージする) ことができる技術である。UnionFS を完全に書き換えるもので、信頼性とパフォーマンスの改善を狙いとしている。

とのことですが、このあとに

DebianUbuntu 系には aufs を利用し、ファイルシステムへの変更をメモリ ( tmpfs ) に保存するためのツール、fsprotec が入っている。これにより、実験的に設定ファイルを変更した状態でサーバを動作させ、再起動さえすれば元の状態に復元出来る、といった使い方も可能になる。コンテナ型仮想化技術であるDockerでは aufs をストレージドライバとして利用し、同技術の特徴である差分管理を実現している。

いろいろ調べてみるとDockerで使用するストレージドライバに問題があるような感じのようです。

さらにググってみたらこういう情報が!

stackoverflow.com

この情報を元にDockerの起動オプションでストレージドライバの変更を行います。Dockerの起動オプションを設定するファイルは/lib/systemd/system/docker.serviceとなるのでこれを編集して--storage-driver=vfsを追加することにします。

$ sudo vim /lib/systemd/system/docker.service

次の行を追加します。

…(略)...
ExecStart=/usr/bin/dockerd --storage-driver=vfs -H fd://
…(略)...

実際に追加したファイルは次の様になります。

[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
BindsTo=containerd.service
After=network-online.target firewalld.service containerd.service
Wants=network-online.target
Requires=docker.socket

[Service]
Type=notify
# the default is not to use systemd for cgroups because the delegate issues still
# exists and systemd currently does not support the cgroup feature set required
# for containers run by docker
ExecStart=/usr/bin/dockerd --storage-driver=vfs -H fd:// --containerd=/run/containerd/containerd.sock
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always

# Note that StartLimit* options were moved from "Service" to "Unit" in systemd 229.
# Both the old, and new location are accepted by systemd 229 and up, so using the old location
# to make them work for either version of systemd.
StartLimitBurst=3

# Note that StartLimitInterval was renamed to StartLimitIntervalSec in systemd 230.
# Both the old, and new name are accepted by systemd 230 and up, so using the old name to make
# this option work for either version of systemd.
StartLimitInterval=60s

# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity

# Comment TasksMax if your systemd version does not supports it.
# Only systemd 226 and above support this option.
TasksMax=infinity

# set delegate yes so that systemd does not reset the cgroups of docker containers
Delegate=yes

# kill only the docker process, not all processes in the cgroup
KillMode=process

[Install]
WantedBy=multi-user.target

起動が終わったらDockerサービスをリスタート(PCの再起動でも可)してDockerのテストコマンドを実行すると

Dockerサービスのリスタートコマンドは以下の通りです。

$ sudo systemctl restart docker

【Dockerのテスト実行の結果】

$ sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
1b930d010525: Pull complete 
Digest: sha256:2557e3c07ed1e38f26e389462d03ed943586f744621577a99efb77324b0fe535
Status: Downloaded newer image for hello-world:latest

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

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

見事に実行できました!ありがとう!

おわりに

Live環境でも通常OSと環境的には大差ないだろうと思っていたのですが、やっぱりそうでもなかったということでした。 しかし、はまりどころに良くあたる…これで2日つぶした。

(注意)ストレージドライバをaufsからvfsに変更するとDockerのパフォーマンスがかなり悪くなります。特にイメージをpullするときやbuildするときなどは顕著な様です。Live環境なのでそんな酷使することはないと思いますが、そのあたりを頭に入れて使用するのが良いようです。

【ChromeOSインストール版】Dockerを公式チュートリアル”Get Started with Docker”で学ぶ【挫折編】

前回の続きでChromeOSにDockerをインストールしてみたので公式のドキュメントを見ながら学んで見たいと思います。

【関連エントリ】

uepon.hatenadiary.com

今回はPart3の部分になります。(本当はPart4まで進めたかったが)

ただ、読みすすめるとわかると思いますが、うまくいきません。

原因は未だにわからないのですが、同じPCで別のOS(UbuntuDebianCentOSのそれぞれUSB起動のLive版で確認)では起動できているのでChromeOSならではの問題かと思います。

Get Started, Part 3: Services

ここではServiceに関して学ぶことになります。

事前準備

事前準備としてはDockerのインストールは当然のことながらPart1とPart2を行ってきた環境(コンテナ)があることが必要です。

加えてDocker Composeのインストールが必要になります。Mac用のDockerデスクトップとWindows用のDockerデスクトップにはプレインストールされていますが、Linux系は別途インストールする必要があります。以下のサイトを参考にインストールを行います。

docs.docker.com

$ sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose

【インストールログ】

$ sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   617    0   617    0     0    859      0 --:--:-- --:--:-- --:--:--   858
100 11.2M  100 11.2M    0     0  2250k      0  0:00:05  0:00:05 --:--:-- 2910k

$ sudo chmod +x /usr/local/bin/docker-compose

$ docker-compose --version
docker-compose version 1.23.2, build 1110ad01

また、Part2で作成したfriendlyhelloイメージをレジストリにpushして公開することが必須になります。(ここではその共有イメージを使います。) 以下のようにDockerのイメージを実行し、http://localhost:8080/という感じでアクセスできることは確認しておいてください

※原文は4000ポートを使用していますがChromeOSではアクセスがNGなので8080に変更してありますので注意です。

【一般形】

$ docker run -p 8080:80 username/repo:tag

【前回の例で以下のように動作していました(ここでいうhogeは各自のユーザ名に変更してください)】

$ docker run -p 8080:80 hoge/get-started:part2

サービスとは?

このPartではアプリケーションを拡張し負荷分散を行います。そのために分散アプリケーションの階層を1レベル上に上げ、サービスという概念が必要になります。

階層的にはこんな感じ

  • Stack
  • Services (←いまここ)
  • Container (part2(前回)でやった部分)

分散アプリケーションはサービスという概念で構成されていて、例えばビデオ共有サイトでは、データベースサービス、ビデオをトランスコーディングするサービス、フロントエンド用のサービスから構成されているようなイメージになります。

サービスはコンテナから構成されます。その設定でイメージの実行方法、使用するポート、コンテナのレプリカをいくつ実行する必要があるかなどの体系化を行う必要があります。これらの設定を行う場合にはdocker-compose.ymlを記述することでこれらの定義、実行、拡張することが可能になります。

docker-compose.ymlを書いてみる

docker-compose.ymlでコンテナの動作を設定してみます。

以下に記載してymlファイルdocker-compose.ymlとして作成します。 このとき、Part2で作成したイメージをレジストリにpushしていることを確認して、自分の作成した username/repo:tagをイメージの詳細に置き換えてこの.ymlを更新します。 以下の例ではhoge/get-started:part2として設定しています。またポートも4000から8080へ変更しています。

【変更前(原文のまま)】

version: "3"
services:
  web:
    # replace username/repo:tag with your name and image details
    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:

【今回の変更後】

version: "3"
services:
  web:
    # replace username/repo:tag with your name and image details
    image: hoge/get-started:part2
    deploy:
      replicas: 5
      resources:
        limits:
          cpus: "0.1"
          memory: 50M
      restart_policy:
        condition: on-failure
    ports:
      - "8080:80"
    networks:
      - webnet
networks:
  webnet:

このdocker-compose.ymlでは次のような動きを記述しています。

  1. Part2で作成したイメージをリポジトリから取得。
  2. 取得したイメージから5つのインスタンスを生成して、webと呼ばれるサービスとして名づけます。それぞれに対してCPU資源は10%、50MBのRAMを割当て。
  3. コンテナでエラーがあった場合には再起動。
  4. webサービスの各インスタンスの80ポートはローカルホストからは8080ポート経由からアクセス可能。(オリジナルの記述は4000ポート)
  5. webnetと呼ばれる負荷分散ネットワークを生成し、ポート80を共有するようにwebのコンテナに指示します。 (内部的にはコンテナ自体が一時ポートでwebのポート80に発行)
  6. webnetネットワークをデフォルト設定(負荷分散オーバーレイネットワーク)で定義。

負荷分散ネットワークアプリの起動

まずは以下のコマンドを起動します。コマンドの意味はPart4で説明がありますが、これを実行しないとエラーが発生します。

$ docker swarm init

つづいて以下のように実行します。この実行ではアプリはgetstartedlabと設定しています。

$ docker stack deploy -c docker-compose.yml getstartedlab

【ログ】

$ docker swarm init
Swarm initialized: current node (lj9h2t6lfbifgh9xs3ade0d5g) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-15hfgnp2gthm9vvlu3sk7l628c8q8dthz639bpv3f1ziqogx6w-1djxiz46bjblng15l3qhno4cq 100.115.92.198:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

$ docker stack deploy -c docker-compose.yml getstartedlab
Creating network getstartedlab_webnet
Creating service getstartedlab_web

サービスでは5つのコンテナを起動しているのでそれを確認します。確認方法は以下のようになります。

$ docker service ls

【ログ】

$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE                      PORTS
padrydlvz757        getstartedlab_web   replicated          0/5                 hoge/get-started:part2   *:4000->80/tcp

おや?0/5ってなってる…起動できていない…いつまでたってもこの状態…

サービス内で実行されているコンテナはタスクと呼ばれます。タスクには、docker-compose.ymlで定義したレプリカの数まで、数値が増加する固有のIDが与えられます。 ってことなんですが、起動と再起動を繰り返しているっぽい。

$ docker service ps getstartedlab_web
ID                  NAME                      IMAGE                      NODE                DESIRED STATE       CURRENT STATE                 ERROR                              PORTS
so2dl24jsz9g        getstartedlab_web.1       hoge/get-started:part2   penguin             Ready               Assigned 3 seconds ago                                           
zl2amqebzsaq         \_ getstartedlab_web.1   hoge/get-started:part2   penguin             Shutdown            Failed 5 seconds ago          "starting container failed: fa…"   
8ok1l8k9t5qc         \_ getstartedlab_web.1   hoge/get-started:part2   penguin             Shutdown            Rejected 41 seconds ago       "error creating external conne…"   
miejmm2yf3r6         \_ getstartedlab_web.1   hoge/get-started:part2   penguin             Shutdown            Rejected about a minute ago   "error creating external conne…"   
tlhz1mzxkw4g         \_ getstartedlab_web.1   hoge/get-started:part2   penguin             Shutdown            Failed about a minute ago     "starting container failed: fa…"   
t0a3hi9trnev        getstartedlab_web.2       hoge/get-started:part2   penguin             Ready               Assigned 3 seconds ago                                           
ajg4c4ws13f7         \_ getstartedlab_web.2   hoge/get-started:part2   penguin             Shutdown            Failed 5 seconds ago          "starting container failed: fa…"   
klph7llfrxzd         \_ getstartedlab_web.2   hoge/get-started:part2   penguin             Shutdown            Failed 33 seconds ago         "starting container failed: fa…"   
lxb65skkypfp         \_ getstartedlab_web.2   hoge/get-started:part2   penguin             Shutd

コンテナ数を変更しても、Dockerfileを編集して構成を変えてもだめ。ChromeOSではこれ以降はうまくできないようです。(2019.03.18現在) Swarm系の動作はダメなんでしょうか…

現状進めることはできないので、とりあえず以下のコマンドで終了させます。

$ docker stack rm getstartedlab
$ docker swarm leave --force

挫折してみて…

原因はまだよくわかっていませんが、本来の目的はDockerを学ぶことなので放置します。 ChromeOSに関してはやっぱり通常のLinuxとは違うのでしょうか。sshなターミナルマシンとしてはいい感じなんですけどなー

次はOSをUbuntu18.04 LTSのUSB Live版に変更して継続していこうと思います。 もう一回同じPartから進めていこうと思います。

Windows10のデフォルトコマンドでUSBメモリのパーティション関連をクリーンにする

USBメモリを使用してLinuxなどのLive環境やChromeOSのインストールなどを使用していると、そのUSBメモリの再利用するために 再フォーマットをしようとするとパーティション関連が特異な状態になってしまいWindowsの【ディスクの管理】からでは 結構面倒な操作をする必要などが出てきてしまいます。

f:id:ueponx:20190316154950p:plain

単純にRaspbianなどのイメージ書き込みするだけであれば、そこまでの必要性はないのすが…UNetbootinなどのLiveUSB作成ツールでは そこまでやってくれないので自分で対応する必要がでてくるわけです。

【参考】 unetbootin.sourceforge.net

もう少し楽な感じでやれないかなと思って調べてみました。

diskpart.exeを起動する

cmd.exeもしくは【ファイル名を指定して実行】(Winキー + r)からdiskpart.exeを起動します。

f:id:ueponx:20190316155059p:plain

するとdiskpart.exeが起動します。

f:id:ueponx:20190317081339p:plain

接続されたディスクのリストを表示して、作業対象になるUSBメモリのディスク番号を確認します。

ディスク一覧を表示する

DISKPART> list disk

このリストの中からUSBメモリ確認した番号でを選択し作業を行っていくことになります。

f:id:ueponx:20190317081357p:plain

番号指定はしっかりと確認しましょう。間違えるとウルトラやばいです。(パーティションを消してしまいますのでホント気をつけましょう)

作業対象のディスクを選択する

続いて実際に作業対象の設定を行います。

DISKPART> select disk [ディスク番号]

f:id:ueponx:20190317081433p:plain

ディスク情報の詳細を確認する

念の為、選択したディスクの情報を表示し、間違いないことを確認します。

DISKPART> detail disk

f:id:ueponx:20190317081458p:plain

ディスクから構成情報を削除する

ディスクのパーティション情報をクリアします。

DISKPART> clean

cleanコマンドでディスクから構成情報を全て削除します。 (実行するとパーティーションが何もない状態になります。)

f:id:ueponx:20190317081627p:plain

実行したら念の為ディスクの詳細を確認します。

DISKPART> detail disk

detail diskで表示を確認する。

f:id:ueponx:20190317095121p:plain

プライマリパティーションを作成する。

これでUSBはきれいな状態になっているのでプライマリパーティションを作成して使えるようにしていきます。

DISKPART> create partition primary

f:id:ueponx:20190317081716p:plain

あとは、通常の手順で好きなフォーマットでフォーマットすればOKです。

【フォーマット前】 f:id:ueponx:20190317081745p:plain

FAT32でフォーマット後】 f:id:ueponx:20190317081751p:plain

おわりに

いろんなOSのUSB起動をやっているとUSB内のパーティション構造が特殊になっていくのでその対応に関して調べてみました。 一般的な使い方をしている場合には、そんなに使わなくてもいいはずなんですが…

【ChromeOSインストール版】Dockerを公式チュートリアル”Get Started with Docker”で学ぶ【前編】

ChromeOSにDockerをインストールしてみたので公式のドキュメントを見ながら学んで見たいと思います。

【参考】 uepon.hatenadiary.com

正直QiitaなどではDockerのMAC版の資料は検索に引っかかってくるのですが、LinuxというかChromeOSでの情報が見つかりにくい状況だったので、 いいタイミングなので公式のチュートリアルをみながら学んで見ようと思います。

【公式チュートリアル

docs.docker.com

f:id:ueponx:20190227225438p:plain

Get Started, Part 1: Orientation and setup

このパートではDockerの説明とセットアップに関して記載されています。

チュートリアルの流れは以下のようになっているようです。

  1. Docker環境を設定する
  2. 1つのコンテナとしてイメージを構築する
  3. 複数のコンテナを実行するようにアプリをスケールする
  4. クラスタ全体にアプリを分散する
  5. バックエンドデータベースを追加してスタックサービス化
  6. アプリをプロダクションに

個人的には3つ目ぐらいで理解の限界がきそうなのですが、できるだけ頑張ってみます。

この章の最初の方は以下の説明がされています。

  • Dockerとはどんなものなのか?
  • イメージとコンテナとは?
  • コンテナとVMの違い

そのあたりは結構わかりやすいサイトもあるのでググっていただければ…とにかく利点はいっぱいあるのでとりあえず進めていきましょう。

インストール

DockerにはCEとEEの2つのエディションがあります。個人で使うとなるとCE(Community Edition )になると思います。EEはEnterprise Editionの略です。 (注)インストールするときにKubernetesを使う前提であればバージョンに注意が必要のようです。

インストールに関しては過去のエントリを参考にしてください。WindowsとかOSXな方はインストーラがあるので大丈夫でしょう。

【参考:Dockerのインストール】

uepon.hatenadiary.com

参考のエントリ内で、インストール後にバージョンの確認やhello-worldイメージの実行などは行っていますので、このパートはこれで終わりとなります。

Get Started, Part 2: Containers

次のパートででは実際にコンテナを作成して行くお話になります。 前提としてはDockerのインストールと動作に関しては事前確認しておくことですが、前パートが動作できていればOKです。

Dockerで考えるアプリはコンテナ、サービス、スタックというレイヤーがあり、その一番下層にくるのがコンテナになるそうです。

  • Stack
  • Services
  • Container (←ここをやる)

これまでの開発では環境づくりが結構めんどくせー感じだったので、それを解決するんだ〜って感じの内容になっています。 Dockerを使えば、開発環境はイメージの取得だけで、インストールは不要。開発側のアプリも、依存関係、そしてランタイムを含めて移行できるみたいです。

これらのイメージの設定はDockerfileと呼ばれるものによって定義します。 Dockerファイルで定義されていればどの環境に行っても同じ実行環境を再構築できるというのがメリットです。環境構築するのが目的ではないのでこれは便利!

今回のチュートリアルではpythonのアプリケーションを通してコンテナの扱い方を学びます。 まずは作業を行うディレクトリを作成して、移動します。

$ mkdir friendlyhello
$ cd friendlyhello

移動したディレクトリでdockerfileを作成していきます。チュートリアルのdockerfileのテキストをそのまま使用すればいいのですが、今回はこれを少し変えて進めます。 チュートリアルではdockerfileに記述するベースイメージがpython:2.7-slimとなっていますが、ChromeOSではリソースが小さいため Alpine Linuxpythonイメージを使用することになりました。これでかなりのディスクスペースの軽量化も図れます。

alpinelinux.org

【dockerfile】

# Use an official Python runtime as a parent image
# FROM python:2.7-slim # オリジナルの記述
FROM python:2.7-alpine

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# Make port 80 available to the world outside this container
EXPOSE 80

# Define environment variable
ENV NAME World

# Run app.py when the container launches
CMD ["python", "app.py"]

これで定義できました。あとは、開発するアプリの本体app.pyと依存関係の部分(pipのインストール設定)requirements.txtを設定します。 基本的にはチュートリアルそのままで大丈夫です。

【requirements.txt】

Flask
Redis

今回のアプリではRedisというデータベースを使用しています。

Redisは、ネットワーク接続された永続化可能なインメモリデータベース。連想配列、リスト、セットなどのデータ構造を扱える。いわゆるNoSQLデータベースの一つ。オープンソースソフトウェアプロジェクトであり、Redis Labsがスポンサーとなって開発されている。(Wikipediaより

redis.io

自分も初めて使用するものなのであんまりよくわかっていないですが、自分が前々から欲しかった機能はこれかも。今後はRedisを使っていこうかな。

【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)

ここまでできて、ディレクトリの内容が以下のような状態になったことを確認します

$ ls
Dockerfile      app.py          requirements.txt

続いてはdockerfileの情報をもとにビルドを行います。

$ docker build --tag=friendlyhello .

※ 末尾のドット.も忘れずに!

--tagオプションまたは-tオプションで作成するイメージにタグ付けができるので、今回はfriendlyhelloという名前をつけています。

【ログ】

$ docker build --tag=friendlyhello .
Sending build context to Docker daemon   5.12kB
Step 1/7 : FROM python:2.7-alpine
 ---> 028b1c040d1e
Step 2/7 : WORKDIR /app
 ---> Using cache
 ---> eae50d1926e5
Step 3/7 : COPY . /app
 ---> 5769976c4b92
Step 4/7 : RUN pip install --trusted-host pypi.python.org -r requirements.txt
 ---> Running in 19bcbd6b1e65
DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7.
Collecting itsdangerous>=0.24 (from Flask->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/76/ae/44b03b253d6fade317f32c24d100b3b35c2239807046a4c953c7b89fa49e/itsdangerous-1.1.0-py2.py3-none-any.whl
Collecting Jinja2>=2.10 (from Flask->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/7f/ff/ae64bacdfc95f27a016a7bed8e8686763ba4d277a78ca76f32659220a731/Jinja2-2.10-py2.py3-none-any.whl (126kB)
Collecting MarkupSafe>=0.23 (from Jinja2>=2.10->Flask->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/b9/2e/64db92e53b86efccfaea71321f597fa2e1b2bd3853d8ce658568f7a13094/MarkupSafe-1.1.1.tar.gz
Building wheels for collected packages: MarkupSafe
  Building wheel for MarkupSafe (setup.py): started
  Building wheel for MarkupSafe (setup.py): finished with status 'done'
  Stored in directory: /root/.cache/pip/wheels/f2/aa/04/0edf07a1b8a5f5f1aed7580fffb69ce8972edc16a505916a77
Successfully built MarkupSafe
Installing collected packages: Werkzeug, click, itsdangerous, MarkupSafe, Jinja2, Flask, Redis
Successfully installed Flask-1.0.2 Jinja2-2.10 MarkupSafe-1.1.1 Redis-3.2.0 Werkzeug-0.14.1 click-7.0 itsdangerous-1.1.0
Removing intermediate container 5d012755c159
 ---> 9fbdfb77b37c
Step 5/7 : EXPOSE 80
 ---> Running in cc219cd8b3ab
Removing intermediate container cc219cd8b3ab
 ---> 4b2f986a13a9
Step 6/7 : ENV NAME World
 ---> Running in 69f49359148c
Removing intermediate container 69f49359148c
 ---> a602cc6d6905
Step 7/7 : CMD ["python", "app.py"]
 ---> Running in 005ad4362edb
Removing intermediate container 005ad4362edb
 ---> 1ad3fbdd80bf
Successfully built 1ad3fbdd80bf
Successfully tagged friendlyhello:latest

Dockerファイルでは以下のようなことを行っています。

  1. イメージの取得(ローカルにない場合にはDocker Hubから取得)
  2. 作業用ディレクトリの設定
  3. ローカルで開発したアプリのファイルをイメージにコピー
  4. pipでアプリが依存するモジュール環境構築
  5. 公開ポートの設定
  6. 環境変数の設定
  7. アプリの実行

イメージ作成結果の確認すると…

$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
friendlyhello       latest              1ad3fbdd80bf        40 minutes ago      73MB
python              2.7-alpine          028b1c040d1e        4 days ago          61.2MB

イメージが2つになっていますが、Alpain Linuxのベースイメージを取得してから、それに追加する形でオリジナルのイメージを作成するので、 ローカル側には実質2つのイメージが保存されます。

タグがlatestになっています。 tagオプションの完全な構文は--tag = friendlyhello:v0.0.1と記述するようです。 ベースにAlpine Linuxのイメージを利用していることもあり、イメージのサイズも非常に小さくなっています。

ここまででimageの作成が完了できたので、以降は実行となります。

注意点

Proxyサーバ経由の環境の場合

Proxyサーバの設定が必要な場合にはdockerfileに以下の行を追加してProxyのポートを設定する必要があります。

# Set proxy server, replace host:port with values for your servers
ENV http_proxy host:port
ENV https_proxy host:port

DNSの設定ミスがあった場合

DNSの設定ミスでpipの実行がエラーになった場合には /etc/docker/daemon.jsonに以下のようにDNSの設定を追加してdockerのプロセスを再起動し、再度buildを行ってください。

/etc/docker/daemon.json

{
  "dns": ["your_dns_address", "8.8.8.8"]
}

【dockerサービスの再起動】

$ sudo service docker restart

アプリケーションの実行

いよいよアプリケーションを実行します。実行にはdocker runコマンドを使用します。 その際、-pオプションを使用して、ローカルマシンのポート4000をアプリケーションのコンテナマシンのポート80にマッピングします。 この指定を行うことでローカルマシンのhttp://localhost:4000/のアクセスを行うとコンテナマシンのhttp://localhost:80/にアクセスしたことと同じように動作させることができます。多分Linuxでこの動作を行う際はポート番号に注意が必要のようです。port4000ではエラーが発生したので、port8080などの比較的大きな番号のものに差し替えたほうがいいかと思います。

$ docker run -p 8080:80 friendlyhello

【ログ】

$ docker run -p 8080:80 friendlyhello
 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:80/ (Press CTRL+C to quit)

動作後にローカルPC側のブラウザからhttp://localhost:8080/へアクセスします。

f:id:ueponx:20190227225542p:plain

【ログ】

$ docker run -p 8080:80 friendlyhello
 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:80/ (Press CTRL+C to quit)
172.17.0.1 - - [26/Feb/2019 15:12:43] "GET / HTTP/1.1" 200 -
172.17.0.1 - - [26/Feb/2019 15:12:45] "GET /favicon.ico HTTP/1.1" 404 -

動作が確認できたらコンソールで【Ctrl+C】として終了します。

アプリはデタッチモードでも起動することもできます。(イメージ起動後がバックグラウンドで行われています。)

$ docker run -d -p 8080:80 friendlyhello

実行を確認するためにはdocker container lsを実行して状況を確認します。

$ docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
56b0547a29b9        friendlyhello       "python app.py"     37 seconds ago      Up 33 seconds       0.0.0.0:8080->80/tcp   determined_joliot

このように表示されます。この状態で先ほどと同様にブラウザからのアクセスにも応答され動作を確認することもできます。

デタッチモードでの動作を終了する場合には、docker container lsコマンド実行時に表示されたCONTAINER IDを使用してdocker container stopコマンドで停止を行います。

【ログ】

$ docker run -d -p 8080:80 friendlyhello
56b0547a29b91f3ec0c14f02ce5beea34b67d7db24e415419270e25547c59d97

$ docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
56b0547a29b9        friendlyhello       "python app.py"     37 seconds ago      Up 33 seconds       0.0.0.0:8080->80/tcp   determined_joliot

$ docker container stop 56b0547a29b9
56b0547a29b9

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

f:id:ueponx:20190227225819p:plain

あれ?

先程、Dockerのコンテナの実行を行いましたが、なにか変な感じしませんでしたか?

f:id:ueponx:20190227225542p:plain

counter disabledってなんだよw なんと!公式のドキュメント内でも同じ表示になっています。

f:id:ueponx:20190228003531p:plain

この部分はapp.pyから使用されているRedisが動作できていないことに起因しているようです。 簡単に言うとRedisはインストールしているのにredis-serverを起動していないので、Redisの処理に失敗しているというわけです。

こりゃいかんということでdockerfileの見直しとアプリを起動するシェルスクリプトを作成します。 一見dockerfile内の末尾にあるCMDのエントリにredis-serverの起動を追加すればいいのではないかと思うのですが、CMDは設定に含まれる最後の1行のみが実行されるので 起動シェルを作成するのがよさそうです。

【startup.sh ファイル作成】

#!/bin/sh
redis-server &
python app.py

作成したファイルに実行権限付与をします。

chmod +x ./startup.sh

【dockerfile 要修正】

# Use an official Python runtime as a parent image
# FROM python:2.7-slim # オリジナルの記述
FROM python:2.7-alpine

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host pypi.python.org -r requirements.txt \
    && apk --update --no-cache add redis

# Make port 80 available to the world outside this container
EXPOSE 80

# Define environment variable
ENV NAME World

# Run app.py when the container launches
# CMD ["python", "app.py"] # オリジナルの記述
CMD ["./startup.sh"]

アプリ側の記述も修正を行います。Redisのホストとポート名の引数を変更・追加しています。

【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)
redis = Redis(host="localhost", port=6379, 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)

再ビルド、再実行を行う

f:id:ueponx:20190301082302p:plain

ブラウザでアクセスすると…

f:id:ueponx:20190301082319p:plain

無事動作できました。

作成したイメージの共有

こうして作成したイメージは別環境でも使用できるようにレジストリに登録して共有を行えるようにすれば利便性が上がります。 Gitに似たようなものだと思います。Dockerにもオリジナルのレジストリがありますが、それ以外のものも使用することは可能です。

まずは以下のサイトでDocker.Hubのユーザー登録を行います。

hub.docker.com

ユーザー登録が終わったら、コマンドラインで以下を実行し、Dockerの公開レジストリにログインを行います。

f:id:ueponx:20190303113325p:plain

無事にログインができたらレジストリへのレポジトリの関連付けを行うタグ登録を行います。 推奨のタグ付けの規則としてはusername/repository:tagという形式を使うようです。

【一般化】

$ docker tag image username/repository:tag

【例:ユーザー名がhogeの場合】

$ docker tag friendlyhello hoge/get-started:part2

実行がうまく行くと新しいタグイメージが生成されます。(確認はdocker image lsで行えます)

$ docker tag friendlyhello hoge/get-started:part2
$ docker image ls
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
hoge/get-started   part2               6321d6a7a1cd        2 days ago          74.4MB
friendlyhello        latest              6321d6a7a1cd        2 days ago          74.4MB
python               2.7-alpine          028b1c040d1e        8 days ago          61.2MB

f:id:ueponx:20190303113503p:plain

つづいてはイメージの公開になります。

【一般化】

$ docker push username/repository:tag

【例:ユーザー名がhogeの場合】

$ docker push hoge/get-started:part2

f:id:ueponx:20190303113423p:plain

この処理が完了するとpushしたイメージは一般に公開されます。以下のように実行すればローカルにイメージがなければ ネットからイメージを取得して実行が行われます。

$ docker run -p 8080:80 hoge/get-started:part2

実際にイメージを削除して実行すると以下のようになります。

f:id:ueponx:20190303113130p:plain

f:id:ueponx:20190303113020p:plain

とりあえず、これでパート2は終了です。

おわりに

長くなったので一旦ここで終了です。実際に触ってみた感じですが、ぼっち開発な自分でもかなり使えそうな感じです。 問題があるとすればネット回線が太いところでないとむずかしいのかなと。

【関連】 uepon.hatenadiary.com

/* -----codeの行番号----- */