はじめに
私のエンジニアとしての初仕事はDockerでした。辛かったのをいまでも思い出します
みなさんこんにちは、Watanabe Jin(@Sicut_study)です。
みなさんはエンジニア始めたての時にどんなことで苦労したでしょうか?
- GitHub
- Docker
- Kubernetes
- AWS
など色々あるかと思いましたが、「環境構築」というのは多くの人がつまづく箇所かと思います。
プログラミングの勉強をするにはそもそもの開発環境がないとできないことも多いです。
またAWSなどのクラウドを利用してデプロイをするときにも再度登場して苦しめられます。
今回はそんな初心者には考え方や使いどころがわかりづらいDockerについて例え話を活用しながら説明していきたいと思います。
Dockerが難しいと思うのは、「概念がよくわからない」「説明を読んでも使いどころのイメージがつかない」というのがあるかと思います。
この記事では「例え話」「ハンズオン」を駆使しながら解説しております。
エンジニアをやる上でDockerの技術は必須だと考えます。ぜひとも使えるようになっていただいてご自身の勉強に活用いただけたらと思います。
対象読者
- Dockerという言葉を聞いたことがある人
- Dockerを少し使ったことがある人
- 環境構築を楽に行いたい人
- エンジニアとして1つレベルアップしたい人
- 難しい説明で挫折した人
目次
- Dockerとは何か?なぜ使うのか?
- Dockerの導入
- コンテナを起動してみる
- Dockerfileでイメージを作ろう
- APIをイメージで起動してみる
- Docker Composeで楽に起動しよう
1. Dockerとは何か?なぜ使うのか?
まずはよくあるDokerの説明から行います(知らない方は難しいやつです)
Dockerは、コンテナ技術を利用してアプリケーションを効率的にデプロイ、スケール、および管理するためのプラットフォームです。Dockerは、ソフトウェアを「コンテナ」と呼ばれる独立したユニットにパッケージ化します。このコンテナは、アプリケーションコード、ランタイム、ライブラリ、および依存関係をすべて含んでおり、どの環境でも一貫して動作します
個人的にDockerが難しいのは「Docker自体」を勉強しようと本を読んだり、ネットを調べても難しい言葉がたくさんあってイメージがつかないことです。
今回はゲーム(Switch)を使って例え話をします
では先程の文章をゲームで例えてみましょう
Dockerをゲーム機(Switch)に例えます。ゲーム機では色々なゲームソフトをストアからインストール(ソフトの購入)できます。このゲーム1つ1つをコンテナといい、それぞれのゲームにはすでにプログラミングがされており、使う人はどんなコードで書かれているか理解しなくてもゲームを起動するだけで遊べます。また他人のゲーム機(他のDocker環境)にソフト(カートリッジ)を入れても同じく動作します
Docker(Switch)で複数のコンテナ(ゲーム)が存在する |
カセットはどのSwitchでも動作する |
なんとなくDockerについてのイメージがついたところで なぜ使うのか? を説明します
Dockerを利用することで以下のようなメリットがあります。
効率的なデプロイ
ゲームソフトをカートリッジにパッケージ化しておくことで、どのゲーム機でも簡単にそのゲームをプレイできるようになります。
同様に、Dockerコンテナを使えば、どのコンピュータでも簡単にアプリケーションをデプロイできます。
Dockerという環境があれば常にコンテナは動かすことができるというのが重要で、ローカルでコンテナが起動できるならクラウド上のDockerでも起動できます
スケーラビリティ
Dockerは同じアプリケーションの複数のインスタンスを簡単にスケール(増やす)することができます。これにより、多くのユーザーが同時にアプリケーションを利用できるようになります。
これはゲームで例えるのが少し難しかったので実際の例を説明します。
もしあなたのWebサイトが突然テレビで紹介されて多くのユーザーがアクセスしたとします。
そうなるとアクセスが増加して普通であればアプリケーションは遅くなります。
しかし、コンテナであればいくらでもコンテナを起動することが可能です
複数コンテナを起動することでアクセスを1つのコンテナでなく、分散させることができるのです
あえて例え話をするなら「フランチャイズのお店」をイメージしてください
とある有名なラーメン店が1店舗あったとします。
そのお店1つではお客さんを長く待たせてしまうので、フランチャイズ(コンテナ)でお店を作ってお客さんを分散させることにしました
このお店は料理やオペレーションなどがすべてマニュアル化されているので、同じお店を作ることが容易でした
管理のしやすさ
それぞれのゲームソフトが独立しているため、特定のゲーム(アプリケーション)が他のゲームに影響を与えません。
同様に、Dockerコンテナは独立した環境を提供するため、各アプリケーションが他のアプリケーションに影響を与えることなく管理できます。
ポケモンにバグがあったとしても、ドラゴンクエストには影響を及ぼすことはないのです
2. Dockerを導入してみよう
ここからはみなさんのパソコンにDockerの環境を用意しましょう
Dockerの環境構築は私の中で大きく2つの方法があります。
- Docker Desktopを利用する
- コマンドでインストールをする
あまりLinuxコマンドに慣れていないのであれば、まずはDocker Desktopを利用することをおすすめします。
インストールするだけでDocker環境を構築することができます
もしコマンドに慣れているのであればそちらでも大丈夫です
インストールが完了したら以下のコマンドをターミナルで叩いてみます
$ docker -v
Docker version 23.0.1, build a5ee5b1
このように表示されれば大丈夫です。
3. コンテナを起動してみる
いまあなたの目の前にはゲーム機があります。
ソフトを買って起動したくてワクワクしているはずです
ここからは実際にコンテナの起動をしながらより理解を深めていきます。
まずは以下のコマンドを実行してみてください
$ docker pull hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
c1ec31eb5944: Pull complete
Digest: sha256:94323f3e5e09a8b9515d74337010375a456c909543e1ff1538f5116d38ab3989
Status: Downloaded newer image for hello-world:latest
$ docker run hello-world
Hello from Docker!
まずはゲームソフトをネットから買ってくる操作をしました
ネット上には多くのイメージ(ゲームソフト)があります。
今回は「hello-world」というゲームソフトをpull
コマンドで取得しました
次にrun
コマンドでイメージを起動します。Docker上にイメージを元にコンテナが起動します
今回のイメージ(hello-world)はHello from Docker!
と表示(echo)するものだったのでターミナルに表示されました
では次にデータベースのイメージを起動してみようと思います。
イメージはDocker Hubに色々あります。好きなソフトを簡単に起動できるのでDockerさえあれば簡単にDBも利用できます。(本来ならコマンドで色々インストールして構築する必要があります)
$ docker pull postgres
$ docker run --name postgres -e POSTGRES_PASSWORD=mysecretpassword -d postgres
いくつかみなれないものがでてきました
- --name : コンテナにpostgresと名前をつけました
- -e : 環境変数POSGRES_PASSWORDにmysecretpasswordと設定しました
- -d : バックグラウンド起動 (これがないとログがたくさんでる)
docker runではコンテナに対して設定ができるような引数を使うことができます
DBの環境をもつコンテナが起動してので早速使ってみます
まずはコンテナが起動できているかを確認します
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b2c5f83206fc postgres "docker-entrypoint.s…" 4 minutes ago Up 4 minutes 5432/tcp postgres
docker ps
でいまDocker環境にあるコンテナの一覧をみることができます
ここではpostgresという名前でコンテナができていることがわかります
$ docker exec -it postgres bash
まずはコンテナに入ります。
コンテナはいわば小さなPCのようなもので、そのPCにはすでにDBに必要なものがすべて含まれています。
そのPCの世界に入ることでDBが利用できます。
docker exec -it コンテナ名 bash
でコンテナの中には入れます
入れたらあとはローカルでPosgresをインストールシているときと同じように使えます
root@b2c5f83206fc:/# psql -U postgres -d postgres
postgres=#
exit // Postgresqlから抜ける
exit //コンテナから抜ける
これでローカルにPostgresを入れたときと同じようなことがコンテナを利用してできました。
本来であれば大変だった環境構築もイメージを利用することで簡単です
ここで疑問が湧いた人もいるかもしれません
hello-worldのコンテナがdocker psで表示されない!!
コンテナには実行した状態と終了した状態があります。
hello-worldコンテナは、非常にシンプルなコンテナで、実行するとすぐに終了するように設計されています。
起動が終了しているので表示されなかったのです
Postgresはデータベースサーバーです。
これは、常に実行されている必要があります。アプリケーションがデータを保存したり取得したりするために、データベースサーバーが常に稼働している必要があるからです。なので常に実行中なのでpsコマンで表示されました
docker exec -it
でコンテナに入りましたが、このコマンドで入れるコンテナは起動中のものになります。起動が終わるとPC自体もなくなってしまいます
4. Dockerfileでイメージを作ろう
ではここからはイメージ(ゲームソフト)を自分で作成してみて、Dockerの中で起動してみましょう
ここではゲームの仕様書であるDockerfileというものを作成します
Dockerfileは、Dockerイメージをビルドするための設計図です。テキストファイル形式で、各ステップを記述していきます。Dockerfileには、ベースイメージ、アプリケーションのソースコード、依存関係のインストール方法、実行コマンドなどが含まれています。
Dockerfileの基本構成:
FROM:ベースイメージを指定します。
WORKDIR:作業ディレクトリを設定します。
COPY:ローカルファイルをコンテナ内にコピーします。
RUN:コマンドを実行します(例:パッケージのインストール)。
CMD:コンテナが起動されたときに実行されるデフォルトのコマンドを指定します。
少し難しいと思うので作りながら理解していきましょう
まずは先程使ったhello-world
を自作してみます
$ touch Dockerfile
# ベースイメージとしてAlpine Linuxを使用
FROM alpine:latest
# コマンドを実行して「Hello, World!」を表示
CMD ["echo", "Hello, World!"]
Dockerfileの最初のFrom
にイメージを書いています
これはネットにあるイメージです
基本的には0からDockerfileを作ることはなく、他の人が作ったイメージの上からカスタマイズをしてくことが基本になります。
イメージとしては、Switchの「マリオメーカー」といったものでしょうか
ゲーム自体の基本はあってユーザーはその機能を利用して新しいゲームを作成することができます
ではこの仕様書を使ってゲームソフトにしていきます
$ docker build -t my-hello-world .
$ docker run my-hello-world
最初のコマンドで設計書からゲームソフトを作成します
そしてrunで起動すると同じものが起動できるはずです。
5. APIをイメージで起動する
みなさんの手元にはJavaScriptのフレームワークであるExpressが起動できる環境はありますでしょうか?
なくても大丈夫です。みなさんの手元にはDocker環境があるためExpressの環境があるイメージ(ゲームソフト)があれば実行ができます
$ mkdir docker-express
$ cd docker-express
$ touch app.js
次にjsファイルにexpressのAPIサーバーを起動するコードを書いてみます
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('Hello, World!');
});
app.listen(port, () => {
console.log(`App listening at http://localhost:${port}`);
});
せっかくなので試しに手元で起動してみましょう
もし手元にNode環境がなければ用意して試してほしいですが、DockerがあればNodeがなくてもできますので試すかはおまかせします
$ npm init -y
$ npm i express
$ node app.js
最後はcrtl+Cで止めてください (このあと3000番ポートが利用できなくなります)
これでサーバーが起動したのでcurlでAPIを叩いてみます
curl localhost:3000
Hello, World
ローカルでexpressのAPIを起動できました
ではこれをDockerfileを使って行いたいと思います。
$ touch Dockerfile
# ベースイメージを指定
FROM node:14
# 作業ディレクトリを設定
WORKDIR /usr/src/app
# package.jsonとpackage-lock.jsonをコピー
COPY package*.json ./
# 依存関係をインストール
RUN npm install
# アプリケーションのソースコードをコピー
COPY app.js .
# アプリケーションを実行
CMD [ "node", "app.js" ]
ではDockerfileを解説していきます
まずはFrom node:14
というすでにネットにあるNode.jsのイメージを土台に用意しています。これを用意することでローカルにNode環境があるという前提を実現させます
次にWORKDIR /usr/src/app
でcd /usr/src/app
のようなことをしています
そのあとに先程ローカルで作成したpackage.json
を/usr/src/app
にコピーします。package.jsonには先程npm iでいれたexpressが入っています
{
"name": "docker-express",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.19.2"
}
}
なのでわざわざDockerfile内でnpm i express
などはしなくてもコピーだけで問題ありません
次にCOPY app.js .
でコンテナの/usr/src/app
にいまいるディレクトリdocker-express
のapp.js
をコピーします
最後にアプリケーションの起動をします
CMD [ "node", "app.js" ]
としていますが、CMD
で書くことでコンテナ起動時に実行されるコマンドをかくことができます
ここまでみるとローカルで起動するまでに行った手順とほとんど変わらないことがわかります
では実際にイメージからAPIを起動してみましょう
# my-node-appというイメージ名で作成
$ docker build -t my-node-app .
# 起動
$ docker run -p 3000:3000 --name my-node-app my-node-app
-p 3000:3000
という見慣れないものがでてきました
これはDocker コマンドのオプションで、ホストマシンとコンテナの間でポートをマッピング(バインド)するために使用されます。
これにより、ホストマシンの特定のポートにアクセスすると、そのリクエストがコンテナ内の対応するポートにリダイレクトされます。
このオプションがないとコンテナ(PC)の中ではlocalhost:3000
で起動していますが、コンテナと今私たちが操作しているローカルの世界はまったくの独立した世界になるのでローカルのlocalhost:3000
とコンテナのlocalhost:3000
は別物になるのです
そこでホストマシンのlocalhost:3000にアクセスしたら自動でコンテナのlocalhost:3000に繋いでくれるようにするのが-pコマンドなのです
# 別ターミナルを開く
curl localhost:3000
Hello, World!
では試しに-pをつけずにも試してみましょう
まずは起動中のコンテナを止めてきます
$ docker stop my-node-app
$ docker rm my-node-app
$ docker run --name my-node-app my-node-app
コンテナを止めるにはstop
コマンドが利用できます(ゲームソフトを終了させることができます)
コンテナは止めても再起動できる状態でソフト自体はゲーム機に残っています(ダウンロードしたソフトはいつでも遊べる)
なのでrmコマンドでコンテナを完全削除します(ゲームのアンインストール)
ではアクセスしてみます
$ curl localhost:3000
curl: (7) Failed to connect to localhost port 3000: 接続を拒否されました
-p 3000:3000
がないためホストマシン(ローカル)の3000番ポートにアクセスしてもコンテナの3000番にはつながらないため接続拒否されました
試しにコンテナの中に入って同じコマンドを叩いてみましょう
$ docker exec -it my-node-app bash
$ curl localhost:3000
Hello, World!
コンテナのなかであればlocalhost:3000でAPIを使うことができるためうまくいきました
6. Docker Composeで楽に起動しよう
あなたがAPIをこれからexpressで作るとなると、expressのコンテナとDB(postgres)のコンテナ2つを開発のたびに起動する必要があります
$ docker run -p 3000:3000 --name my-node-app my-node-app
$ docker run --name postgres -e POSTGRES_PASSWORD=mysecretpassword -d postgres
しかしこのコマンドかなり長いし、コンテナを1つ1つrun
で起動しないといけないですし大変だと気づきます
そこで登場するのがDocker compose
というツールです。Docker Composeは、複数のDockerコンテナを定義し、実行することができます。
さっそくインストールをしましょう
$ docker compose version
Docker Compose version v2.27.1
Docker ComposeはYAML
というファイル形式で設定を書くことができますので、試しに書いてみましょう
$ touch docker-compose.yml
version: '3'
services:
web:
build: .
container_name: my-node-app
working_dir: /usr/src/app
volumes:
- .:/usr/src/app
ports:
- "3000:3000"
depends_on:
- db
db:
image: postgres:13
container_name: postgres-db
environment:
POSTGRES_PASSWORD: mysecretpassword
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
色々わからないことが多いと思うので解説します
my-node-app
my-node-appはすでにDockerfileがあるので利用してコンテナを起動するように設定を書きました
build: .
はDockerfile(./Dockerfile)をもとに起動することを書いています。Dockerfileは自明なので.
のみで場所を教えればDockerfileを探して起動してくれます(コマンドでいうとdocker run my-node-app)
container_name
でコンテナに名前つけます (--name my-node-app)
working_dir
でコンテナに入ったときの最初の位置を設定できます (docker exec -it my-node-app bashをするとこのディレクトリの位置にいきます)
volumes
ではローカルのディレクトリ(docker-express)とコンテナのusr/src/appをマウントしています。これによりdocker-expressにファイルが追加されたら、コンテナにも作成されます。ディレクトリの内容が同期された状態になります
ports
はコマンドの-p 3000:3000を表しています
depends_on
は起動する際の依存関係を書いており、db
という名前のコンテナが起動してからこのコンテナは起動することが設定されています
DB(postgres)
DBの起動はDockerfileを利用せず、すでに用意されているイメージを利用していました
その場合はこのようにかけます
image: postgres:13
postgresでは環境変数を与える必要があるのでenvironment
を使って環境変数の設定を行いました (コマンドの-e POSTGRES_PASSWORD=mysecretpassword)
またDBはコンテナを停止してもデータは消えてほしくありません
そこでデータを永続化させるために以下の設定を行います
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
それではさっそく起動していきます
# 先程起動したコンテナを消しておく
$ docker sotp my-node-app
$ docker rm my-node-app
$ docker compose up
# 別ターミナルで実行
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5d5b1011d1db docker-express-web "docker-entrypoint.s…" 5 seconds ago Up 4 seconds 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp my-node-app
c2ab945ab873 postgres:13 "docker-entrypoint.s…" About a minute ago Up 4 seconds 5432/tcp postgres-db
設定した2つのコンテナをコマンド1つで簡単に起動できました
これで長いrunコマンドとはおさらばです!
おわりに
今回はたとえ話をしながらDockerというものついて解説して
実際にハンズオンを行いながらDockerで色々な環境を起動する体験をしていただきました
Dockerを使えるようになると一気に実力をあげることができます。
大変な環境構築が楽になり、クラウドへのデプロイもできるようになります。
私が成長したのはDockerを理解したところからだったのでぜひハンズオンを生かしてDockerを試していただけたらと思います。
ここまで読んでいただけた方はいいねとストックよろしくお願いします。
@Sicut_study をフォローいただけるととてもうれしく思います。
また明日の記事でお会いしましょう!
JISOUのメンバー募集中
プログラミングコーチングJISOUではメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか?
気になる方はぜひHPからライン登録お願いします👇