792
1027

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

私のエンジニアとしての初仕事はDockerでした。辛かったのをいまでも思い出します

みなさんこんにちは、Watanabe Jin(@Sicut_study)です。

みなさんはエンジニア始めたての時にどんなことで苦労したでしょうか?

  • GitHub
  • Docker
  • Kubernetes
  • AWS

など色々あるかと思いましたが、「環境構築」というのは多くの人がつまづく箇所かと思います。
プログラミングの勉強をするにはそもそもの開発環境がないとできないことも多いです。

またAWSなどのクラウドを利用してデプロイをするときにも再度登場して苦しめられます。

今回はそんな初心者には考え方や使いどころがわかりづらいDockerについて例え話を活用しながら説明していきたいと思います。

Dockerが難しいと思うのは、「概念がよくわからない」「説明を読んでも使いどころのイメージがつかない」というのがあるかと思います。
この記事では「例え話」「ハンズオン」を駆使しながら解説しております。

エンジニアをやる上でDockerの技術は必須だと考えます。ぜひとも使えるようになっていただいてご自身の勉強に活用いただけたらと思います。

対象読者

  • Dockerという言葉を聞いたことがある人
  • Dockerを少し使ったことがある人
  • 環境構築を楽に行いたい人
  • エンジニアとして1つレベルアップしたい人
  • 難しい説明で挫折した人

目次

  1. Dockerとは何か?なぜ使うのか?
  2. Dockerの導入
  3. コンテナを起動してみる
  4. Dockerfileでイメージを作ろう
  5. APIをイメージで起動してみる
  6. Docker Composeで楽に起動しよう

1. Dockerとは何か?なぜ使うのか?

まずはよくあるDokerの説明から行います(知らない方は難しいやつです)

Dockerは、コンテナ技術を利用してアプリケーションを効率的にデプロイ、スケール、および管理するためのプラットフォームです。Dockerは、ソフトウェアを「コンテナ」と呼ばれる独立したユニットにパッケージ化します。このコンテナは、アプリケーションコード、ランタイム、ライブラリ、および依存関係をすべて含んでおり、どの環境でも一貫して動作します

個人的にDockerが難しいのは「Docker自体」を勉強しようと本を読んだり、ネットを調べても難しい言葉がたくさんあってイメージがつかないことです。

今回はゲーム(Switch)を使って例え話をします

では先程の文章をゲームで例えてみましょう

Dockerをゲーム機(Switch)に例えます。ゲーム機では色々なゲームソフトをストアからインストール(ソフトの購入)できます。このゲーム1つ1つをコンテナといい、それぞれのゲームにはすでにプログラミングがされており、使う人はどんなコードで書かれているか理解しなくてもゲームを起動するだけで遊べます。また他人のゲーム機(他のDocker環境)にソフト(カートリッジ)を入れても同じく動作します

                                       
image.png
Docker(Switch)で複数のコンテナ(ゲーム)が存在する
                                       
image.png
カセットはどのSwitchでも動作する

なんとなくDockerについてのイメージがついたところで なぜ使うのか? を説明します

Dockerを利用することで以下のようなメリットがあります。

効率的なデプロイ

ゲームソフトをカートリッジにパッケージ化しておくことで、どのゲーム機でも簡単にそのゲームをプレイできるようになります。

同様に、Dockerコンテナを使えば、どのコンピュータでも簡単にアプリケーションをデプロイできます。

Dockerという環境があれば常にコンテナは動かすことができるというのが重要で、ローカルでコンテナが起動できるならクラウド上のDockerでも起動できます

スケーラビリティ

Dockerは同じアプリケーションの複数のインスタンスを簡単にスケール(増やす)することができます。これにより、多くのユーザーが同時にアプリケーションを利用できるようになります。

これはゲームで例えるのが少し難しかったので実際の例を説明します。
もしあなたのWebサイトが突然テレビで紹介されて多くのユーザーがアクセスしたとします。

そうなるとアクセスが増加して普通であればアプリケーションは遅くなります。
しかし、コンテナであればいくらでもコンテナを起動することが可能です

複数コンテナを起動することでアクセスを1つのコンテナでなく、分散させることができるのです

image.png

あえて例え話をするなら「フランチャイズのお店」をイメージしてください

とある有名なラーメン店が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サーバーを起動するコードを書いてみます

app.js
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/appcd /usr/src/appのようなことをしています

そのあとに先程ローカルで作成したpackage.json/usr/src/appにコピーします。package.jsonには先程npm iでいれたexpressが入っています

package.json
{
  "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-expressapp.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コマンドなのです

image.png

# 別ターミナルを開く
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番にはつながらないため接続拒否されました

image.png

試しにコンテナの中に入って同じコマンドを叩いてみましょう

$ 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
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からライン登録お願いします👇

792
1027
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
792
1027

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?