前回から引き続いて「Linuxをマスターしたい人のための実践Ubuntu」のハンズオン的な章の実践記です。
今回はDockerでUbuntuを立ててみます。
Linuxというよりはコンテナについてがメインの記事です。
前提
- サーバーが使えること。
筆者は前回と同じくEC2を利用しました。EC2の立て方は以下を参考にしてください。
セキュリティグループ(SG)のインバウンドルールは以下のようにしています。自分のIPからのみアクセスを受け付けるようにしています。今回は通信暗号化を行わなかったので、実際はHTTPSの穴あけは不要です。
インストール
レポジトリをアップデートし、docker.ioをインストールします。「docker」というパッケージは別物らしいので注意してください。
インストールするとDockerデーモン(dockerd)が起動します。Dockerデーモンに対してクライアントがRESTful API経由で通信することでコンテナを操作する、というのがDockerの基本アーキテクチャらしいです。
→KubernetesのKubernetesAPI(Kubectlを使って操作する)みたいな感じですかね?
ubuntu@ip-172-31-40-251:~$ sudo apt update
ubuntu@ip-172-31-40-251:~$ sudo apt install -y docker.io
Ubuntuの場合、Dockerデーモンと通信できるのはrootユーザーかdockerグループに属しているユーザーのみです。現在は「ubuntu」というユーザーでログインしているので、ubuntuというユーザーの権限を変更します。
docker.sockというファイルはDocker デーモン(サービス)とコミュニケーションを行うための Unix ソケットファイルです。このソケットを通じて、Docker CLI(コマンドラインインターフェース)や他のプログラムは、Docker デーモンに命令を送ったり、情報を受け取ったりします。
そしてソケットファイルというのは、通常のデータファイルとは異なり、データの読み書き先(エンドポイント)として機能するファイルです。つまり、このファイルにデータを書き込むと、そのデータはソケットに接続された別のプロセスで読み取ることができる...らしいです。(沼にはまりそう......)
とにかく、dockerCLIを使いたければ/var/run/docker.sockにアクセス権が必要なんだな、というふうに理解していれば大丈夫なはず。
権限を確認 → ubunbuユーザーをdockerグループに追加 → 追加したグループを反映
という操作を行います。
ubuntu@ip-172-31-40-251:~$ ls -l /var/run/docker.sock
srw-rw---- 1 root docker 0 Aug 27 05:53 /var/run/docker.sock
ubuntu@ip-172-31-40-251:~$ sudo gpasswd -a ubuntu docker
Adding user ubuntu to group docker
ubuntu@ip-172-31-40-251:~$ newgrp - docker
docker versionを実行してエラーが出ないことを確認します。
ubuntu@ip-172-31-40-251:~$ docker version
Client:
Version: 20.10.25
API version: 1.41
Go version: go1.20.3
Git commit: 20.10.25-0ubuntu1~22.04.2
Built: Thu Aug 10 20:13:18 2023
OS/Arch: linux/amd64
Context: default
Experimental: true
Server:
Engine:
Version: 20.10.25
API version: 1.41 (minimum version 1.12)
Go version: go1.20.3
Git commit: 20.10.25-0ubuntu1~22.04.2
Built: Fri Aug 4 09:20:46 2023
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.7.2
GitCommit:
runc:
Version: 1.1.7-0ubuntu1~22.04.1
GitCommit:
docker-init:
Version: 0.19.0
GitCommit:
ubuntu@ip-172-31-40-251:~$
実行
コンテナを動かすには コンテナイメージ(以後イメージ)が必要です。イメージは、インターネット上にあるイメージが集まったコンテナレジストリという場所にあります。GitHubみたいなもんですね。
dockerコマンドではデフォルトでDocker Hubというレジストリを使用する設定になっています。このDocker Hubには多くの開発元が公式のイメージが存在します。具体的にはNginxやUbuntuのイメージが利用できます。
早速イメージをpullします。新卒ほやほやのころはどういう仕組みでpullしているのかわからなかったですが、普通にインターネット通信をしているんですね。あのころはブラウザ経由でしかインターネットを使えないと思っていたし、シェルなんてそもそもどういう概念かもわかっていなかったので、本当に呪文を唱えているようにしか見えていませんでした。
さて、イメージに関してですが、こいつはイメージ名とタグ名で一意に定まります。タグ名と言っていますが、実質バージョンと考えても問題ないと思います。タグ名を指定しない場合は、最新のものを取ってきてくれます。
ubuntu@ip-172-31-40-251:~$ docker image pull ubuntu:22.04
ubuntu@ip-172-31-40-251:~$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 22.04 01f29b872827 3 weeks ago 77.8MB
コンテナを起動します。-itというオプションをつかうことで、今まで使ってきたコマンド操作みたいなことがコンテナ内でもできるようになります。
-i(--interactive): コンテナの標準入力(stdin)をオープンにし、対話的に操作できるようにします。
-t(--tty): コンテナに仮想端末(TTY)を割り当てます。これによって、コンテナ内でシェルやテキストベースのインターフェイスを使用する際に、より「自然な」操作が可能になります。
/bin/bashの箇所には、コンテナが立ち上がった時に行う最初の処理を書きます。
ubuntu@ip-172-31-40-251:~$ docker container run -it ubuntu:22.04 /bin/bash
root@000e5556b7cf:/#
実行中のコンテナを表示します。別ターミナルを開き、そこから実行中のコンテナを確認します。
ubuntu@ip-172-31-40-251:~$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
000e5556b7cf ubuntu:22.04 "/bin/bash" About a minute ago Up About a minute quirky_chaum
コンテナを終了します。exitコマンド(ctrl + d)でも終了できますが、実際のプロダクトで使うコンテナはバックグラウンドで起動し、内部のシェルを直接操作しません。そこでより実践的なコマンドである「docker container stop [containerID]」を使います。
停止したコンテナを確認します。ステータスがExitedになっています。
ubuntu@ip-172-31-40-251:~$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
000e5556b7cf ubuntu:22.04 "/bin/bash" 10 minutes ago Exited (137) 2 minutes ago quirky_chaum
停止したコンテナを削除します。
ubuntu@ip-172-31-40-251:~$ docker container rm <containerID>
コンテナ内でWebサーバーを動かす
nginxのDockerイメージをpullします。
ubuntu@ip-172-31-40-251:~$ docker image pull nginx
ホームディレクトリに「www」というディレクトリを作成し、テスト用のHTMLファイルも作成します。
ubuntu@ip-172-31-40-251:~$ mkdir ~/www
ubuntu@ip-172-31-40-251:~$ echo 'Hello nginx!' > ~/www/index.html
コンテナを立ち上げます。コンテナ内にボリュームをマウントして起動します。
-v または --volume オプションは、ホストマシン(この場合は ubuntu@ip-172-31-40-251)とコンテナ間でデータボリュームをマウント(共有)します。このオプションにより、ホストマシンの ~/www ディレクトリ(ホームディレクトリの www サブディレクトリ)がコンテナ内の /usr/share/nginx/html ディレクトリにマウントされます。これにより、このディレクトリにある HTML ファイルなどが Nginx によってホスティングされます。
→おおもとのホストに存在するディレクトリ(~/www)をコンテナ上のディレクト(/usr/share/nginx/html)にマウントしているんですね。
-p または --publish オプションは、ホストのポートとコンテナのポートをマッピング(転送)します。この例では、ホストマシンの 8080 ポートがコンテナ内の Nginx サーバーの 80 ポートにマッピングされています。これにより、ホストマシンの 8080 ポートにアクセスすると、実際にはコンテナ内の Nginx サーバーの 80 ポートにアクセスしていることになります。
-d: これは --detach の略で、このオプションを指定するとコンテナはバックグラウンドで実行されます(デタッチモード)。コマンドラインがすぐに返ってきて、その後もコンテナがバックグラウンドで動き続けます。
ubuntu@ip-172-31-40-251:~$ docker container run -v ~/www:/usr/share/nginx/html -p 8080:80 -d nginx
ubuntu@ip-172-31-40-251:~$ echo 'Hello nginx!' > ~/www/index.html
今回はローカルからEC2にSSH接続し、そのEC2上にコンテナが立っています。そのため、ドメインは「localhost」ではなくEC2のパブリックIPを指定します。
コンテナイメージ作成
先ほどDocker Hubからとってきたイメージですが、実は取ってくるだけじゃなく、作ることもできます。イメージを作るには、その設計書であるDockerfileというファイル名のファイルをdocker image buildというコマンドでビルドすることでイメージを作成します。ビルドの際Dockerfileのパスを指定していないので、おそらくそのファイルが存在するディレクトリ内でdocker image buildをしてやらないといけないはずです。
では、作業用ディレクトリを作成し、Dockerfileを作成し、Dockerイメージをビルドします。
-tオプションで、イメージに名前とタグが付けられます。
ubuntu@ip-172-31-40-251:~$ mkdir ~/hello-image
ubuntu@ip-172-31-40-251:~/hello-image$ vi Dockerfile
ubuntu@ip-172-31-40-251:~/hello-image$ cat Dockerfile
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y hello
ENTRYPOINT ["/usr/bin/hello"]
ubuntu@ip-172-31-40-251:~/hello-image$ docker image build -t hello:latest ~/hello-image
ビルドしたDockerイメージを確認します。
ubuntu@ip-172-31-40-251:~$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
hello latest fbd0e8e7adee 6 minutes ago 122MB
nginx latest eea7b3dcba7e 10 days ago 187MB
ubuntu 22.04 01f29b872827 3 weeks ago 77.8MB
ubuntu@ip-172-31-40-251:~$
ビルドしたDockerイメージを起動します。Hello, worldと出ていれば成功です。
ubuntu@ip-172-31-40-251:~/hello-image$ docker container run hello
Hello, world!
感想
ぼんやりとしか理解できていなかったコンテナですが、実際に手を動かしてみることで理解が深まりました。百聞は一見にしかずといいますが、ITの技術は百回ドキュメントを見るよりも一回触ってみるほうがグンと理解が深まることを実感しました。