ペンギン村 Tech Blog

技術をこよなく愛するエンジニア集団が在住するペンギン村から、世界へ役立つ(かもしれない)技術情報を発信する技術系ブログです。某アラレちゃんが済む村とは一切関係ありません。んちゃ!

Dockerハンズオンをやってみた - Kubernetes輪読会#0

どうも、Nintendo Switch版の斑鳩が配信されるの楽しみな tobi462(過去記事) です。

斑鳩はSTGとしての面白さは当然のこと、硬派なストーリー設定も素晴らしく・・・おっとこれを語りだすと長くなるのでやめておきましょう。

Kubernetes輪読会

さて、ペンギン村のエンジニア数人で Kubernetes の輪読会をすることになりました。

輪読会の対象となる技術書は以下です。

Kubernetes は Google が公開した OSS のコンテナオーケストレーションツールですが、GCP(Google Cloud Platform) に続いて、AWS(Amazon Web Service)も公式にサポートし、実質的にコンテナオーケストレーションツールの標準として君臨したと言えます。

オーケストレーションとは、負荷に応じてコンテナの数を変更して負荷分散を図ったり、アプリケーションの差し替えを用意にしてくれる仕組みのようです。つまり、コンテナ単体では面倒だったことをいろいろサポートしてくれるようです。(このあたりの理解は怪しいです、なにせこれから輪読会を通じて学ぶのですから)

始動 #0

さて、そんなわけで始めることになったのですが、ペンギン村のエンジニアはモバイルに属しているエンジニアが殆どなので、実のところ Kubenetes 以前に Docker についてもあまり馴染みがありません。

そんなわけで初回は #0 ということで、Docker の勉強会的なものをやることになりました。さすがに Kubernetes などのオーケストレーションを理解する前に Docker などのコンテナ技術の基礎は必須だよね、という流れです。

今回は Docker についてある程度知識がある tobi462(過去記事) がハンズオンを開催することで、 Docker やコンテナ技術の基本的なところを理解しあおうという事になりました。

コワーキングスペース

というわけで初回は以下のコワーキングスペースを利用させていただきました。

co-forest.com

コワーキングスペースによっては静かで喋りづらい雰囲気もあったりするのですが、ここは「会話OK」と紹介されていてディスカッションもしやすそうだったので選ばせていただきました。

打ち合わせっぽい会話が聞こえてきたりもしますが、決してガヤガヤしすぎて集中できないというレベルではないのでなかなかバランスの取れた場所なのかなと思います。以前は Swift の TDD を検証するペアプロでも利用させていただいており、こういったディスカッションしつつ何かをやりたいときには絶好の場所だなと感じています。

今回、#0 を開催して結構気に入ったので、今後の輪読会もここで開催していこうかという話になっています 。(他にも良い場所があったら是非教えてください)

Docker ハンズオン

さて、そんなわけで今回のメインコンテンツである Docker ハンズオンです。

外部ディスプレイに映して、それを見ながらやってもらおうと考えていたのですが、ディスプレイケーブルを忘れてしまったので Slack に随時書き込みながら進める形にしました。

実際には口頭で補足説明もしながらだったので、文章にすると多少情報は欠けてしまうのですが、せっかくなので記事に書かせていただこうと思います。

インストール

最初に以下をインストールしてもらいました。

実のところ Docker のハンズオンとしては後半2つは不要なのですが、ハイパーバイザ型とコンテナ型で起動速度の違いを実感してもらおうという意図と、便利だからせっかくだし入れちゃおうぜみたいな感じで一緒にインストールしてもらいました(おぃ

Docker 入門

講師:
 まずは伝統の Hello world からにしましょうか。ターミナルで以下のコマンドを叩いてみてください。

$ docker run hello-world

講師:
 なにやらダウンロードされた後に Hello World 的なものが表示されたと思います。 docker run は指定したイメージをコンテナとして起動するコマンドで、今回は hello-world というイメージ名を指定しています。Docker は指定したイメージがなければ自動的に Dockerレジストリからイメージをダウンロードして実行してくれる仕組みになっています。

 さて、今回の起動したコンテナを見てみましょう。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
1b0a41707472        hello-world         "/hello"            2 minutes ago       Exited (0) 2 minutes ago                       hungry_lovelace

講師:
 -a は終了したコンテナも表示するというオプションで、今回のように Hello world を出力した後に終了するコンテナを表示する場合には指定が必要になります。

 出力されたカラムの意味は以下のとおりです。

カラム 説明
CONTAINER ID コンテナの一意なIDで、自動的に振られる
IMAGE どのイメージをもとにして生成されたコンテナか
COMMAND なんのコマンドが実行されたか
CREATED いつ生成されたか
STATUS どういう状態か。今回は Exited で終了していることがわかる
PORTS ポートの設定が表示されるが、今回は関係ないので表示なし
NAMES コンテナの名前。生成時に指定することも出来るが、そうでなければ自動で振られる。コンテナIDの代わりに使用できる

参加者:
 CONTAINER IDNAMES は起動ごとに変わってるみたいですね。

講師:
 そうですね、同じ Docker Image からいくつでもコンテナを作れるようになっています。これはオブジェクト指向における クラスオブジェクト の関係と考えると分かりやすいかもしれません。

講師:
 せっかくのでイメージの一覧も見てみましょう。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello-world         latest              e38bc07ac18e        6 weeks ago         1.85kB

 それぞれのカラムの意味は以下のとおりです。

カラム 説明
REPOSITORY イメージ名
TAG イメージにつけられるタグで、例えばバージョンを指定できたりします。例えば Swift というイメージがあって、 TAG としては 4.04.1 などに複数あるイメージです。つまりタグを使ってバージョン違いも共存可能ということです。
IMAGE ID イメージの一意なID
CREATED いつ生成されたか
SIZE 最終的なイメージのサイズです。最終的というところがポイントで、ベースのイメージは共有されるようになっています。Gitのコミットグラフにおける親コミットをイメージいただけると分かりやすいかもしれません。

講師:
 さて次にイメージの削除を試してみましょう。

$ docker rmi hello-world
Error response from daemon: conflict: unable to remove repository reference "hello-world" (must force) - container a1ad796bab25 is using its referenced image e38bc07ac18e

 エラーになったと思います。これはそのイメージから作られたコンテナが存在する場合には、警告としてエラーになるようになっているためです。ちなみに rmi の最後の iimage の略だと思います、少なくともそう考えておくと分かりやすいです。

講師:
 ではコンテナを先に削除してみましょう。コンテナのIDの調べ方は・・・ docker ps でしたね、自分の環境にあわせて変更してください。

$ docker rm a1ad796bab25

 何回か起動した場合は複数回必要になるので注意してください。

参加者:
 以下のように複数指定もできるのですね。

$ docker rm 881770fa6d68 6ee4de437fa3
881770fa6d68
6ee4de437fa3

講師:
 全部消したら、先程の rmi をもう一度試してみましょう。今度は消せるはずです。

 うまくいきましたね?

参加者:
 はーい!

講師:
 ところで終了したコンテナを一気に消したいと思う機会は多いはずです。その場合は以下のようにすることで一気に削除できます。

$ docker rm $(docker ps -aq)

 どこかのタイミングで試してみると良いでしょう。-q は コンテナID のみを表示するオプションなので、それで列挙して一気に指定するイメージです。

講師:
 ここからVagrantに移りますが、その前に以下のコマンドでUbuntuを落としておきましょう。

$ docker pull ubuntu

 pull はイメージをDockerレジストリからダウンロードするコマンドです。docker images でローカルにダウンロードされた一覧が見れるはずです。

 試しにTagを指定して pull もしてみましょう。

$ docker pull ubuntu:16.04

 : のあとに指定してるのが Tag で、 docker images でも表示されていたものです。Tag を省略した場合は、 latest というタグが使用されるようになっています。

$ docker images | grep ubuntu
ubuntu              16.04                  0b1edfbffd27        4 weeks ago         113MB
ubuntu              latest                 452a96d81c30        4 weeks ago         79.6MB

 最初にタグなしで pull したものは latest というタグのものがDLされているのがわかるかと思います。

参加者:
 確かに。 Using default tag: latest

<ここで軽くコーヒーブレイク>

Vagrant も触ってみる(ハイパーバイザ型の仮想化を試してみる)

講師:
 さて、コーヒーブレイクが済んだところで(?)Vagrant も触っていきましょう。まずはワークディレクトリを作りましょう。(どこでも良いです

$ mkdir -p ~/Vagrant/Ubuntu_1604

 ワークディレクトリに移動後、以下のコマンドを叩いてみましょう。

$ vagrant init bento/ubuntu-16.04

 そういえば Vagrant の説明を何もしてませんでしたね・・・

参加者:
 Vagrant入門:https://thinkit.co.jp/story/2015/03/19/5740

講師:
 Vagrant は雑に言うと、 仮想環境をCLIで操作できるようにするものです。

 例えば Mac上では、VirtualBox というOracleが提供しているハイパーバイザ型仮想化ソフトウェアがありますが、VagrantはそれをCLIで操ることの出来るものです。

 では以下のコマンドを叩いてVMを起動してみましょう。

$ vagrant up

 ちなみに VMVirtual Machine の略で、すなわち「仮想マシン」です。

 説明が遅くなりましたが、 init したタイミングで Vagrantfile というものが作成されています。

$ ls
Vagrantfile

 これは VM を生成するための料理本(クックブック)のようなもので、ここに設定された内容に従ってVMが立ち上がるようになっています。

 さて、ここで VirtualBox を起動してみましょう。

<という算段だっただが、ダウンロードに時間がかかりすぎたので講師の画面を見せることに>

講師:
 さて、Vagrant up は時間がかかるので先にスクリーンショットを張ってしまいます。

f:id:yu_dotnet2004:20180527205041p:plain

 仮想マシンが Runningになっているのが分かると思います。今回は Vagrant から操作して起動しましたが、VirtualBox の画面から同じことをすることも可能です。しかし、CLIで簡単に VM を立ち上げられるようになっていることが分かると思います。

 以下のようにすることで起動したVMに ssh でログインすることが出来るようになっています。

$ vagrant ssh

参加者:
 なるほど、最近の仮想化ってこんなに簡単になってるんですね。

Docker に戻る(ハイパーバイザ型とコンテナ型の違いについて)

講師:
 では、同じ Ubuntu 16.04 をDockerで起動してみましょう。

$ docker run ubuntu:16.04

参加者:
 は、や、い

<ここからハイパーバイザ型とコンテナ仮想型の違いをメタファを使っていろいろ解説した・・・が、何を喋ったか覚えてないので覚えてる範囲で書き起こし>

講師:
 これがコンテナ型仮想化の強い点で、起動が圧倒的に速いのです。ハイパーバイザ型はハードウェアをエミュレーションするので、実行時のオーバーヘッドが大きいのに比べて、コンテナ型はOSの機能を使ってシミュレートしているイメージで実際にはプロセスとして実行されるようになっています。

 Androidの開発を行った経験がある方は、Androidのエミュレータがすごく重いと感じたことがあると思います。これはエミュレーション、すなわちハードウェアレベルで仮想化しているためで、エミュレータ上で動く Android OS は自身が物理ハードウェアで動いているのかエミュレータで動いているのか区別がつかないのです。

 そう、Matrix のように。

参加者:
 ・・・。

講師:
 はい、面白くなかったですねw

参加者:
 いや、普通になるほどって納得してました。

講師:
 iOS のシミュレータは軽いと感じたことはあると思いますが、これはハードウェアエミュレーションしているわけではなく、シミュレーションで近似値を得ようとしているだけなので計算量が少ないためです。ちょっと強引ですが天気予報みたいなものかもしれません、あれもシミュレーションで近似値を予測している点では似ています。

 そんなわけでハイパーバイザ型はAndroidのエミュレータのように実行時のオーバーヘッドが大きいのです。それに比べてコンテナ型はiOSのシミュレータのようにオーバーヘッドが少ないわけですね。

 コンテナ型仮想化はこのオーバーヘッドの少なさ、起動の速さが流行った要因の一つのような気がします。例えば、AWS Lambda に代表される FaaS はトリガーにより発火するタイミングでコンテナを生成し、その中で関数を実行することで完全な独立性を確保しています。Heroku に代表される PaaS でも裏ではコンテナ型仮想化技術を活用しているケースも多いようです。

 ただ、ハイパーバイザ型仮想化にメリットが無いわけではなく、ホストOSとは異なるOSをインストールすることも出来るという利点もあります。

自分で Docker Image を作る

講師:
 さて、今までは既存の Image を使いましたが、そろそろ自分でもイメージを作りたいですよね?

 まずは作業ディレクトリを作りましょう(どこでもOKです

$ mkdir -p ~/Desktop/DockerWork

 Dockerfile という名前で以下のファイルを作ってみましょう。

FROM ubuntu:16.04

CMD [ "date" ]

 ファイルが作り終わったらイメージをビルドしてみましょう。

$ docker build -t ubuntu_date .

参加者:
 なにやらエラーになりました・・・

講師:  あー、最後の . を忘れないようにしてください。build は最後に . が必要な点に注意しましょう。

講師:
 成功すれば自分がビルドしたイメージが images で表示されるはずですっ!

$ docker images
REPOSITORY          TAG                    IMAGE ID            CREATED              SIZE
ubuntu_date         latest                 4f8b6dc9e250        About a minute ago   113MB

 今回は FROM でベースイメージとして ubuntu:16.04 を指定して、その上に変更をしている感じです。ちなみに ubuntu:16.04 は先程ダウンロードしたはずなので、今回はダウンロードが必要なかったはずです。

 以下のコマンドで確認してみると対応関係が分かりやすいかもしれません。

$ docker images | grep ubuntu
ubuntu_date         latest                 4f8b6dc9e250        6 minutes ago       113MB
ubuntu              16.04                  0b1edfbffd27        4 weeks ago         113MB
ubuntu              latest                 452a96d81c30        4 weeks ago         79.6MB

 では、自分で丹精込めてビルドした Image から コンテナ を起動してみましょう!

$ docker run ubuntu_date
Sat May 26 06:28:17 UTC 2018

参加者:
 これはコンテナ上の Ubuntu で date が実行されているということ?

講師:
 はい、そのとおりです!

講師:
 build でイメージを作成して、 run でイメージからコンテナを作成して起動します。

 ちなみに以下のコマンドでイメージの変更履歴が見れます。

$ docker history ubuntu_date
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
4f8b6dc9e250        10 minutes ago      /bin/sh -c #(nop)  CMD ["date"]                 0B
0b1edfbffd27        4 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
<missing>           4 weeks ago         /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B
<missing>           4 weeks ago         /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$…   2.76kB
<missing>           4 weeks ago         /bin/sh -c rm -rf /var/lib/apt/lists/*          0B
<missing>           4 weeks ago         /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   745B
<missing>           4 weeks ago         /bin/sh -c #(nop) ADD file:592c2540de1c70763…   113MB

 Dockerfile で自分が指定した CMD ["date"] が最後の履歴として表示されているのがわかるかと思います。

<ここで参加者1名が、そのあとの用事のために離脱したので終了>

スペシャルメニュー

<せっかくなのでちょっとしたおまけメニューを用意>

cargo で Rustプロジェクトを作って、それをDockerコンテナ上でビルド・実行してみよう!

FROM rust:1.26.0

WORKDIR /usr/src/myapp

COPY . .

RUN cargo install

CMD [ "hello" ]

cargo の使い方を忘れちゃったかな? 以下で作れるよ。

$ cargo new --bin hello

参加者の感想

参加者A:
 勉強会のモチベーションが分かった
 コンテナがあればサーバーサイドできる気がしてきた

参加者B:
 ハンズオンありがとうございました!わかりやすかったです!

おわり

まぁ今回はこんな感じで、 Kubernetes 読書会 #0 - Docker勉強会、みたいなものをやりました。

実のところ事前準備するつもりが全くできず、ぶっつけ本番になってしまったのですが、そのわりには楽しんでもらえる内容でできたかな、と個人的にはわりと満足しています。

そして連載ブログにしようぜ、という話があがったのはこれをやった後だったので、情報としては欠けてしまった部分も多いかもしれません・・・が、参考になる人もいると思ったので記事にしてみました。

輪読会は続くよ

さて、次回以降はついに Kubernetes の輪読会に入っていきます。楽しみですね。

というわけで、今回は休日の輪読会というか Docker ハンズオンの内容を記事にしてみました。

ではまた。