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

ProtoPediaに作品を登録してみる

ハッカソンに出場すると、作った作品をなにかに残しておきたいことがあります。そんなときにはProtoPediaに作品の情報を残しておきましょう!

作品を一過性ものではなく、記録しておくことで開発の継続や思い出とともに残しておくことができます。また、ハッカソンによっては事前の審査をするために、書いてほしいということもありますが、初めて使うときには戸惑いもあるかもしれません、作品作りに一生懸命になるのも重要ですが、登録自体は簡単なのでぜひ残しておきましょう。

その他、作品の情報も載っているので次の作品の参考にもなるかもしれませんよ。

ProtoPediaprotopedia.net

f:id:ueponx:20190221113408p:plain

今回の項目は以下の通りです。

  • ProtoPediaにログインする
  • 作品を投稿する
  • 登録ができたら
  • 過去に登録した自分の作品一覧を確認する

ProtoPediaにログインする

Protopediaにログインすると以下のような画面になります。

f:id:ueponx:20190220233614p:plain

ProtoPediaは作品の記録ユーザー登録が必要になるので、まずはユーザー登録の作業をします。画面の右上にある【ログイン】ボタンをクリックします。

f:id:ueponx:20190221092954p:plain

クリックすると以下のような画面に遷移します。この画面でSNS連携をしたログインを行うか、それらとは関連の無い登録を行うかを選択することになります。SNS連携したログインを行う場合には以下の登録のステップは不要です。(連携するSNSのアカウントのボタンをクリックして先に進んでください。)

f:id:ueponx:20190220233639p:plain

【アカウント作成】のタブをクリックして登録します。

f:id:ueponx:20190221094040p:plain

クリックするとアカウントの作成画面に遷移します。登録作業はこの画面の右側のフォームに入力をすることになります。

f:id:ueponx:20190220233650p:plain

全パラメータは以下の様になっています。

f:id:ueponx:20190221095431j:plain

必要となる情報は以下の様になっています。赤字は必須項目です。

  • メールアドレス(必須)
  • Username(必須)
  • 名前・姓名(必須)
  • プロフィール写真
  • URL(ブログなどの)
  • 自己紹介文
  • 利用規約のチェック(必須)

入力が終わったら、【アカウントの作成】のボタンをクリックします。これで登録は完了です。

f:id:ueponx:20190221100445p:plain

登録して、ログインすると以下のような画面に遷移します。

f:id:ueponx:20190221113316p:plain


作品を投稿する

ログイン状態になっていなかった場合にはログインを行ってください。

f:id:ueponx:20190221114434p:plain

ログイン状態で画面上部のメニューにある【Prototypes】をクリックしてプルダウンから【投稿する」をクリックする。

f:id:ueponx:20190221101517p:plain

クリックするとPrototypeの作成画面に遷移します。

f:id:ueponx:20190220233807p:plain

フォームは以下の様になっていて

f:id:ueponx:20190221102037j:plain

登録する項目は以下の通りになります。

  • Title(必須)
  • ステータス(必須)【アイデア段階・開発中・完成作品】のいずれか
  • 画像(5画像まで)
  • 動画(動画をアップロードしたURL)
  • Body(作品の概要)
  • API・素材等
  • タグ
  • プロトタイプのURL(もし作品のホームページを作っているようであれば登録)
  • チーム名
  • チームメンバー(一人以上の登録が必須)
  • 連絡先メールアドレス(必須)
  • Wow(この作品のアピール)

API・素材等、タグ、チームメンバーの登録

API・素材等】の入力

入力の補完が行われますので関連するものは出てきます。(前方一致というわけではないようです。)

f:id:ueponx:20190221111020p:plain

【タグ】の入力

タグに関しても関連する技術などを入力しておくといいかなと思います。あとハッカソンによっては、ここにハッカソンの名前などを入力することを言われることもあります。入力中の補完も可能のようです。

f:id:ueponx:20190221111405p:plain

【チームメンバー】の入力

チームメンバーに関してもすでに登録をしていれば、入力の保管(アルファベットのみ)が行われます。自分以外のメンバーにもできればProtoPediaに登録してもらってください。

f:id:ueponx:20190221111412p:plain

メンバーを増やす際は【チームメンバーを追加】ボタンをクリックしてチームメンバーのエントリー行を増やします。

f:id:ueponx:20190221112147p:plain

登録ができたら

登録が終了したら【保存】ボタンをクリックします。事前に【Preview】ボタンを押せば、登録した作品がどのように見えるかわかります。登録したあとも編集はできますので、こまめに編集していくのがいいと思います。

f:id:ueponx:20190221104331p:plain

作品の登録が終わると以下のような画面になります。これで登録は完了となります。

f:id:ueponx:20190222001329j:plain

【編集】のタブをクリックすれば再編集も可能です。

f:id:ueponx:20190221112732p:plain

過去に登録した自分の作品一覧を確認する

ログインした状態で【アカウント情報】をクリックすると、これまでに過去に作られた自分の作品が一覧化された状態で登録が確認できます。

f:id:ueponx:20190222004431p:plain

クリックすると以下のように一覧で表示されます。

f:id:ueponx:20190222005006j:plain

終わりに

ProtoPediaの簡単な使い方を書いてみました。ハッカソンなどでは締切が見えた段階で書くようにいわれるので、事前に使い方を知っておくことや、製作途中でも途中経過を入れておくといいかなと思います。

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