dockerがわからないので公式を読みながらまとめる


環境構築とか各自のPCでやるのめんどくさいし同じ環境作るの大変だし、
デプロイするときにもいろいろ考えなくていいやん?だからdocker使おうよ。

みたいなのはなんとなく知っているんだが、
いざ自分で作るとなるとちゃんと知りたいので公式ドキュメントを読むことにした。
翻訳しつつ、適当にかいつまんでまとめます。

出典: https://docs.docker.com/get-started/

導入

dockerの概念的なおはなし

dockerのコンセプト (ほぼ翻訳)

dockerは開発者やシステム管理者が(リナックス)コンテナを利用して開発・デプロイ・アプリケーションの実行を行えるようにするプラットフォームである。
コンテナ技術自体は新しくないが、アプリケーションを簡単にデプロイするための方法として利用されるのは比較的新しい。

コンテナ技術は

  1. 柔軟: めちゃくちゃ複雑なアプリケーションでもコンテナ化できる
  2. 軽量: コンテナはホスト(dockerが乗っているマシン)のカーネルを共用・活用する
  3. 置換が容易: アプリケーションのアップデートやアップグレードを即デプロイ可能 (デプロイが簡単)
  4. どこでも動く: ローカルマシンでも、クラウドでも、どこでも動かせる
  5. 拡張性: コンテナの複製を作り、自動的に配布(? ちょっと意味がつかめない)することができる
  6. 積み増し可能: コンテナ内に新たなサービスを積み増しするのが簡単

といった特徴のためポピュラーになりつつある。

イメージとコンテナ (ほぼ翻訳)

イメージを実行することでコンテナが起動する。

イメージとは(そのコンテナで動かしたい)アプリケーションを動かすために必要なすべて(= コード、ランタイムモジュール、ライブラリ、環境変数、設定ファイル)を含んだ、実行可能なパッケージである。

コンテナとはイメージの実行時インスタンスである。
つまり、イメージが実行されてメモリに展開された(?)状態をコンテナと呼ぶ。
これは「状態」ないしプロセスを持ったイメージであると捉えて良い。

現在起動中のコンテナのリストはdocker psコマンドで確認できる。

結論、イメージはコンテナの設計図。クラスとインスタンスの関係と似てる
(一緒って言うと突っ込まれそうで日和った)

コンテナとバーチャルマシン (ほぼ翻訳)

コンテナはリナックス上で動作し、同様に動いている他のコンテナとホストマシンのカーネルを共有する。
コンテナは個別のプロセスを利用し、他のアプリケーションと同程度のメモリしか必要としないため、軽量であると言える。

対象的に、バーチャルマシンは完全なゲストOSを動作させ、ハイパーバイザー経由でホストマシンのリソースにアクセスする。
概して、バーチャルマシンは一般的なアプリケーションよりも多くのリソースを持つ環境を提供する。

container virtual machine

コンテナ

コンテナについて

Dockerfile

Dockerfileとはイメージの定義ファイルである。
具体的には、コンテナ内の環境において「何を実行すべきか」「何が存在しているべきか」を定義するファイル。

コンテナ内では、ネットワークインターフェースやディスクドライブなどは仮想化され、ホストマシンとは隔離されている。
そのため、コンテナを定義する際にはポートの繋ぎ先や、どんなファイルがデフォルトで存在しているべきかなどを決める必要がある。

具体的には、Dockerfileには下記のような内容を記載する。

# 公式で用意されているpythonランタイムを親イメージとして利用する
FROM python:2.7-slim

# ワーキングディレクトリを /app に設定する
WORKDIR /app

# カレントディレクトリの中身をコンテナ内の /app にコピーする
COPY . /app

# アプリの実行に必要なパッケージなどをインストールする
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# 80番ポートをコンテナ外からつなげるように開放する
EXPOSE 80

# 環境変数を定義する
ENV NAME World

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

上記でpython:2.7-slimというイメージを読み込んでいるように、イメージは入れ子にすることができる。
つまり、既に定義済みのイメージを利用したイメージを作成することができる (実際に上記ではそれを行っている)

イメージの作成

上記のDockerfileからイメージを作成するためにはビルド(docker build)が必要となる。
ビルド時には--tagオプションをつけることで作成されるイメージに名前をつけられる。

ビルドが完了するとイメージが作成され、ローカルマシン内に保存される (保存形式について詳しく知りたい人はぐぐってください)

イメージからコンテナの起動、及び停止

イメージができると、docker run imageNameコマンドで指定のイメージをコンテナとして立ち上げることができる。
この際、-p localPort:containerPortオプションでポートのマッピング情報を付加できる。
例えば上記のDockerfileでは80番ポートを開放している。
コンテナ内で立ち上がるアプリに対して3000番ポートで接続したい場合には、
docker run -p 3000:80 imageName
というコマンドでコンテナを起動するとよい。

上記コマンドで起動したコンテナは、ctrl + cで停止できる (Macの場合)。

また、コンテナをバックグラウンドで起動したい場合には-dオプションを用いる。
docker run -d -p 3000:80 imageName

この方法で起動した場合、停止する際には明示的に停止するコンテナidを指定する必要がある。

コンテナidはdocker container lsで表示される。

停止コマンドはdocker container stop containerIdとなる。

イメージの共有

ビルドされたイメージは共有が可能である。

共有方法は複数ある(っぽい)が、ベーシックな方法はDocker Hubを用いることである。

Docker Hub: 本家も完全に意識しているが、GitHubっぽくイメージを管理できるWebアプリ

ここでレジストリ、リポジトリという概念が出てきます。

レジストリ: リポジトリを格納するサーバ。つまりDocker Hubはレジストリである。やろうと思えばプライベートレジストリも作れる
リポジトリ: イメージの格納先。Githubのリポジトリと同じ感じ。複数のイメージを格納できる。

タグ付け

docker tag imageName username/repository:tag でタグ付けができる。

公式の例は

docker tag friendlyhello gordon/get-started:part2 のようになっている。

このとき、usernameはDocker Hub上のユーザー名。

タグは必須では無いが、Docker Hubではタグを用いてバージョンをつけるらしいので事実上必須だと思ったほうが良い。

イメージをアップロードする

docker push username/repository:tag

アップロードされたイメージを利用する

一度レジストリにアップロードされたイメージは
docker run -p 4000:80 username/repository:tag
のようなコマンドでpullしつつrunすることができる。

上記コマンド実行時にローカルに該当イメージが存在しなかった場合、dockerは自動的にイメージをpullし、それを実行する。

サービス (2019/6/26追記)

サービスとは、dockerにより配布されるアプリケーションの階層において、コンテナの上位概念にあたるものである。

配布されるアプリケーションを更に細かく分割した単位を「サービス」と呼ぶ。

例えばYouTubeなどの動画シェアサービスを考えると、

  1. アプリケーションデータをデータベースに蓄積するサービス
  2. ユーザーがアップロードしたあとにバックグラウンドで動画のトランスコーディングを行うサービス
  3. フロントエンドをサーブするサービス

などが考えられる (もちろん上記に限らない)。

サービスとはとどのつまり、「実行環境におけるコンテナ(群)」である。

サービスは単一のイメージをコードにより実行する。
どのポートを利用すべきか、サービスが必要とするキャパシティを提供するためにどれだけのコンテナレプリカが必要か、などをコードで指定する。
サービスをスケーリングする、ということの意味は、そのアプリケーションを動かすコンテナインスタンスの数を変動させ、サービスにより多くのコンピューティングリソースを与えるということである。

Dockerが提供するプラットフォームにおいては、docker-compose.ymlを利用することでサービスの概念を実現する。

docker-compose.yml

docker-compose.ymlとは、実行環境においてコンテナ(群)がどのようにふるまうべきかを定義したYAMLファイルである。
当ファイル内でサービスを定義できる。

具体的には下記のように記述する。

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:

上記のdocker-compose.ymlにより、Dockerは下記のようにふるまう。

  • レジストリからusername/repo:tagというイメージをpullする
  • 上記イメージを実行したコンテナを5つ用意し、webという名前のサービスとして実行する。このとき、各コンテナには最大でもCPU1コア分のリソースの10%、及び50MBのRAMを割り当てる
  • コンテナがfail(?)したら即時リスタートする
  • webサービスの80番ポートには、ホストマシンの4000番ポートをマッピングする
  • web内のコンテナは、webnetという負荷分散されたネットワークを通じて80番ポートをシェアさせる (内部的にはコンテナ自身はエフェメラルポートを通じてwebの80番ポートにpublishしている)
  • webnetネットワーク(= 負荷分散されたオーバーレイ・ネットワーク *ちゃんとインフラ知識が無いと理解出来なさそう)はデフォルトのままにしておく

--- また追記します ---