#前提
Dockerについて学んだことを書いていきます。
#本題
#Dockerについて
##優れている点
・ローカル環境でDockerのセットアップが済んでいれば、数行の構成ファイルとコマンドを1つ叩くだけで検証環境が作成される。
・アプリケーションやミドルウェアがすでにセットアップされている状態の仮想環境(Dockerコンテナ)が手早く準備できる。
・Dockerコンテナは仮想化ソフトウェアと比較してより少ないオーバーヘッドで動作する。
・Dockerは開発環境の準備だけでなく、その後の本番環境への展開や、アプリケーションプラットフォームとして機能する。
##旧来型との違い
###【旧来型の場合】
Webアプリケーションを開発する上でApacheやnginxのようなWebサーバーをローカル環境に構築する。
つまり、仮想環境上に本番環境と同じOSをセットアップし、ドキュメントを参考にパッケージマネージャーを操作して必要な物をいれていくといった逐次的な環境構築。
###【Dockerの場合】
ローカルのDocker環境で実行しているコンテナを、別のサーバーにあるDocker環境にデプロイする。
あるいはその逆にサーバーのDocker環境で動作するコンテナをローカルに持ってくることが可能。
つまり、開発環境と本番環境をほぼ同時に再現できる。
##Dockerの苦手な部分
Dockerコンテナの内部はLinux系OSのような構成をしているものが多くを占める。
コンテナはOSとして振る舞いを完全に再現しているわけではない。
より厳格にLinux系OSとして振る舞う仮想環境を構築したい場合は、従来通りVMWareやVirtualBoxといった仮想化ソフトウェアを利用すべき。
またFreeBSDなど非Linuxの環境を動作させたい場合とは目的が違うため、本来Dockerはこれからと競合する存在ではない。
#Dockerの基礎概念
Dockerはコンテナ型仮想化技術を利用している。
コンテナ型仮想化技術自体は、Docker以前から存在する。
Docker登場前はLXC(Linux Containers)が有名。
Dockerは最初期はLXC、現在はrunCというランタイムを用いてコンテナ型仮想化を実現している。
コンテナ型仮想化技術では仮想化ソフトウェアなしにOSのリソースを隔離し、仮想OSにする。
この仮想OSをコンテナ
と呼ぶ。
コンテナを作り出すためのオーバーヘッドは、他の仮想化ソフトウェアと比較して少ない。
高速に起動・終了でき、必要なマシンリソースも少なくて済む。
OS上にインストールした仮想化ソフトウェアを利用し、ハードウェアを演算により再現しゲストOSを作り出す仕組みはホストOS型の仮想化と呼ぶ。コンテナ型仮想化に比べると仕組み上オーバーヘッドが大きくなる。
##アプリケーションにフォーカスしたDocker
LXCはホスト型仮想化技術よりパフォーマンス面で有利なため、システムコンテナとしての用途で一定の地位を確立した。
しかし、LXCでは複製したアプリケーションを別のLXCホストで実行しようとしても、LXCの設定に差異があれば期待した動作が得られないなどの問題がある。
アプリケーションのデプロイ・運用の観点では、機能が不足している。
・ホストに左右されない実行環境(Dockerengineによる実行環境の標準化)
・DSL(Dockerfile)によるコンテナ構成やアプリケーション配置定義
・イメージのバージョン管理
・レイヤ構造を持つイメージフォーマット(差分ビルドが可能)
・Dockerレジストリ(イメージの保管サーバのようなもの)
・プログラマブルな各種API
DockerではDockerfileによりコンテナの情報をコードで管理できるようになった。
このコードをベースに取得や配布の支援も行われていて、再現性が保ちやすいのが特徴。
旧来のLXCとは異なり、Dockerベースでデプロイのスタイルが確立・普及する。
Docker以前はアプリケーションをホストOS、またはゲストOSにデプロイするスタイルが主流。
この方式だと、アプリケーションは実行環境(OS)に強く依存している。
対して、Dockerはコンテナにアプリケーションと実行環境(OS)を同梱してデプロイするスタイルを採用している。
実行環境ごと配布することで、依存問題の困難さを解決している。
##Dockerfile
Dockerがどんなイメージを作成・実行するか定義する。
Dockerfileやアプリケーションの実行ファイルからDockerコンテナの元となるイメージを作ることを、Dockerイメージをビルドするという。
Dockerfileはベース(コンテナの雛形)となるDockerイメージ(OS)をFROMで定義できる。
COPYでは作成したhelloworldファイルをホスト側から、Dockerコンテナ内の/usr/local/binにコピーしている。
RUNはDockerコンテナ内で任意のコマンドを実行できる仕組み。ここではhelloworldスクリプトに実行権限を与えたいる。
ここまでがDockerビルド時に実行され、新たなDockerイメージとして生成される。
CMDは出来上がったイメージをDockerコンテナとして実行する前に行われるコマンドを定義する。
ここは事実上、アプリケーションを実行するコマンドを指定することになる。
FROM ubuntu:16.04
COPY helloworld /usr/local/bin
RUN chmod +x /usr/local/bin/helloworld
CMD["helloworld"]
Dockerfileをもとにビルド、実行。
Dockerfileのあるフォルダーでdocker image buildコマンドを実行。
ビルドが終わったらdocker container runコマンドでDockerコンテナを実行するのが基本的な流れ。
このようにアプリケーションに必要なファイルを、Dockerイメージ(OS)に同梱して、コンテナとして実行していくのがDockerの基本的なスタイル。
今回の例ではシェルスクリプトをubuntuに同梱してコンテナとして実行する。
実際の開発において、Dockerコンテナに配置するアプリケーションはWebアプリケーションやAPIアプリケーションのように常に稼働し続けるプロセスがほとんどを占める。
例えばNode.jsのWebアプリケーションで考えてみるとアプリケーションは起動しっぱなし。
コンテナのビルドも今回の例より複雑になる。
依存するバージョンのNode.jsベースのイメージを利用し、npmでのモジュールインストールやビルド処理をコンテナ内で行ってイメージを完成させる。
実行についてはechoを実行する時と変わらない。出来上がったイメージはDockerさえ実行されていればホスト環境を問わず実行できる。
ホストにNode.jsやnpmをインストールする必要はない。
Dockerを使えばローカル開発環境として必要なアプリケーションを迅速に用意したり、そのままプラットフォームを問わずデプロイしたりできるようになる。
Dockerコンテナによって不変な実行環境が手に入ることで、環境要因のトラブルを最小限にできる。
さらに、WebアプリケーションのフロントエンドにApacheやnginxといったWebサーバを配置するのも、複雑な手順なしにコンテナで設定できるようになる。
ミドルウェアを含めたシステムの構成管理も設定ファイルで定義できる。
環境差異問題からの脱却
アプリケーション開発を指定てこのような事態に遭遇したことはありませんか?
「Bのサーバにも同じアプリケーションをデプロイしたんですが、Aのサーバとでアプリケーションの挙動が違うんですよね・・・」
「うーん、どのサーバにも同じアーカイブを配布しているはずですが・・・」
「サーバの設定や、インストールしているライブラリに差異があるかもしれないよ?」
「あー、Bのサーバにインストールされているライブラリが古かったようです。アップデートしますね!」
「やはり・・・、各サーバの状態を同じように保てるようにする仕組みが必要ですね・・・」
デプロイ先のサーバに差異があって期待された挙動を得られないケース。
この問題の根本的な発生原因は可変的なインフラを許容していることにある。
我々が開発しているアプリケーションは常に何かに依存することは避けられない。
OSはもちろん、CPUやメモリといったコンピュータリソース、言語ランタイム、ライブラリ、アプリケーション内部から別プロセスとして実行されるアプリケーション等、様々な要素に依存している。
各サーバにデプロイしているアプリケーション自体が同じならば、アプリケーションが依存する環境差異を限りなく排除することが、この問題を解決する近道。
##Infrastructure as Code(インフラの構成管理)とImmutable Infrastructure(不変なインフラ)
Infrastructure as CodeとImmutable Infrastructure。
この問題を解決するため、近年強く提唱されているのが
Infrastructure as Code(インフラの構成管理)とImmutable Infrastructure(不変なインフラ)という考え方。
Infrastructure as Codeはコードベースでインフラ構築を定義する考え方。
どのようなサーバの構成にするか、インストールするライブラリ、ツール等は何かをコードベースで定義し、ChefやAnsibelといったプロビジョン二ングツールを使ってサーバを構築する。
手作業が介する余地を減らし、コード中心にすることで、複数の同じ構成のサーバを再現しやすくなる。
ただし、Infrastructure as Codeも万能ではない。
例えば、プロビジョニングツールによって以下のようなコードが実行されるとしよう。
$ nodebrew install-binary stable
Node.jsのバージョン管理ツールであるnodebrewでstable(安定版)のバージョンをインストールする処理。
stableが指すバージョンは頻繁に変わっていくため、このような処理はいつ実行しても同じ結果が得られるとは限らない。
環境差異の問題を避けるには、いつ、何度実行しても同じ結果が保証される冪等性を保つことが重要になる。
アプリケーションが依存するランタイムやライブラリの全ては、確実に特定のバージョンをインストールするようにコードを書くべき。
しかし、インフラ構築がコードベースで管理されていても、冪等性を保証するためのコードを
扱うサーバの台数が多いほど、全てのサーバに構成を適用する時間もかかる。
そこで登場するのがImmutable Infrastructureという考え。
Immutable Infrastructureはある地点のサーバの状態を保存し、複製可能にする考え方。
正しくセットアップされた状態のサーバを常に使えるというのが最大の利点。
サーバに変更を加えたい場合、既存のインフラをアップデートするのではなく作り直して新しいサーバのイメージとして保存し、複製できるようにする。一度セットアップしたサーバは二度と手を加えずに破棄するため、冪等性を気にする必要はない。
Infrastructure as CodeとImmutable Infrastructure、これらの考え方を簡単かつ低コストに実現すつのがDocker。
Dockerfileによって構成を管理するため、Infrastructure as CodeがDockerの大原則となる。
Dockerはコンテナ型の仮想化技術を用いている。ホスト型が仮想マシンのOSを再現するのとは違い、コンテナ型ではOS部分の多くをホストOSと共有している。
その分、起動開始までの時間が数秒と短い。この速さは開発におけるリードタイムの短縮にも大きく寄与する。起動が高速な分、インフラを新しく作り直すImmutable Infrastructreとの相性がいい。
DockerはDockerイメージ(Dockerfile)で構成をコードとして管理でき、既存のコンテナを高速に破棄して新たに構築できる。
Infrastructure as CodeとImmutable Infrastructureの両方の側面を兼ね備えた使い勝手のいいツールと言える。
dockerはdockerイメージ(dockerfile)で構成をコードとして管理でき、既存のコンテナを高速に破棄して新たに構築できる。
Infrastructure as CodeとImmutable Infrastructureの両方の側面を兼ね備えた使い勝手の良いツール。
Dockerのインフラ管理とデプロイの考え方も長所の1つ。旧来の手法では、作成したアプリケーションをどこかのサーバ(インフラ)にデプロイするもので、インフラの再現とアプリケーションのデプロイは完全に分離された作業。
この分断も環境の差異を生み出す温床になっていた。
DockerコンテナはOS(インフラ)とアプリケーションを同梱した箱のようなもの。
Dockerイメージのビルドはインフラとアプリケーションをセットでビルドできること。
分断が起きないために作業環境の差異が生まれづらくなる。
コンテナはDockerイメージとして保存、再利用もできる。
アプリケーションとインフラをセットで管理することから生まれる、ポータビリティの高さはDockerの大きな魅力。
作成したDockerイメージはDockerがインストールされているマシンであればどこでも実行できる。
サーバで実行しているDockerコンテナを開発者のDocker環境でも実行させられる。
SaaS系のCIやCircleCI2.0、CodeshipといったサービスではDockerを使ったCIが可能のため、作成したDockerイメージを使ってE2Eテストをしたりといった応用が可能。
#アプリケーションの構成管理のしやすさ
Dockerコンテナは、アプリケーションとインフラを同梱した箱のようなもの。
一定の規模のシステムは複数のアプリケーションやミドルウェアを組み合わせることで初めて成立する。
つまりいくつかの箱を組み合わせなければ、システムは作れないと言うこと。
システム全体の適切な構成管理が必要になる。
Dockerでこのようなシステムを作ろうと考えた時に、必要なコンテナをそれぞれ実行していく。
デプロイを容易にしたDockerですが複雑なシステムを単体で動かすのは難しい課題。
それぞれのコンテナの依存関係、実行順を間違えると正しく動作しないなど簡単にはいかない。そもそも複数のアプリケーションやミドルウェアを組み合わせて正確に動作させることはDockerを利用していなくても難しいこと。
##Dockerのコンテナオーケストレーションシステム
Dockerではこの困難をすでに解決している。
複数コンテナを利用したアプリケーションの管理をしやすくするため、DockerComposeというツールを提供している。
Docker Composeはyaml形式の設定ファイルで実行するコンテナを定義したり、依存関係を定義して起動順を制御したりできる。
例えば、あるWebアプリケーションがRedisを必要とする場合、WebアプリケーションコンテナとRedisのコンテナの構成を次のように定義して実行できる。
version: "3"
services:
web:
image: gihyodocker/web
ports:
- "3000:3000"
environment:
REDIS_TARGET: redis
depends_on:
- redis
redis:
image: "redis:alpine"
Dockerとdocker composeを介すことで本来複雑だった複数のアプリケーションやミドルウェアの依存関係がコードで簡潔に管理できるようになった。
十分なトラフィックを捌くなど、多くの処理が必要なシステムにおいてはDockerがインストールされたサーバ(Dockerノード)を複数用意し、ノード群に必要なだけのアプリケーションコンテナ群をデプロイする必要がある。
Docker Composeを単一のサーバだけでなく、複数のサーバをまたいで複数のコンテナを管理できるようにしたのがDocker Swarm(Swarm Mode)。
Docker SwarmではDocker Composeでの複数コンテナ群の管理だけではなく、コンテナの増減はもちろん、ノードのリソースを効率化に利用するためのコンテナを配置や負荷分散機能等のさらに実践的な機能が用意されている。
また、デプロイにおいてもローリングアップデート(新旧のコンテナを用意して段階的にサービスインしていく仕組み)が用意されており、運用的なメリットも大きい。
このように複数のノードをまたいで多くのコンテナ群を管理する手法をコンテナオーケストレーションと言う。
非コンテナ環境においてサーバリソースを考慮したスケールアウトやローリングアップデートは、運用や自動化の仕組みをある程度作り込んで実現する必要がある。
しかし、コンテナオーケストレーションでは可用性を確保するための仕組みが当たり前のように組み込まれている。
コンテナオーケストレーションの分野でデファクトスタンダードの地位を確立しつつあるのがkubernetes。Kubernetesは、Google社が長年のコンテナ運用で培われたノウハウを凝縮したOSS。
Docker Swarm以上に機能が充実し、拡張性の高さを持っている。
Dockerはコンテナ単位での利便性だけでなく、Docker ComposeやDocker Swarmといったエコシステムの強力さでシステム開発を十全に行える。
高度なコンテナオーケストレーションの選択肢としてKubernetesもある。
Docker利用をサポートする周辺のプロダクトの充実もDockerの利点。