タケハタのブログ

プログラマの生き方、働き方、技術について雑多に書いていくブログです。

Dockerの復習

最近Kubernetesを覚えようと触っているのですが、その前段階としてDockerの復習もしたので、軽くまとめておきます。
ローカルで構築する手順的な部分だけで、動かすこと優先でやっているので、それぞれの要素も説明とかは浅いです。
すっ飛ばしてる内容もあるので、詳細は別途調べてもらえればと思います。

参考にした資料は下記の書籍です。
gihyo.jp

Dockerで環境構築

Docker for Macのインストール

今回の手順は全てDocker for Macを使用して、Mac PCのローカル環境で実行します。
下記のサイトから、ダウンロードしてインストールしてください。
手順や確認方法も載っています。

docs.docker.com

コンテナとイメージ

Dockerで作る仮想環境は、「コンテナ」という単位で管理します。
そしてそのコンテナを作るのに、イメージというものを使います。

コンテナ・・・仮想環境 イメージ・・・コンテナを作るための設定や情報が入ったもの

みたいなイメージ。
それぞれの詳細と作り方をもうちょっと説明します。

Docker上で動かすサンプルプログラム

まず、Docker上で動かす動作確認用のサンプルプログラムとして下記を作っておきます。
(本当はKotlinでやりたいところですが、一旦手軽でサンプルも多いGoで作ってます)

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main()  {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        log.Println("received request")
        fmt.Fprintf(w, "Hello World!!")
    })

    log.Println("started")
    server := &http.Server{Addr: ":8080"}
    if err := server.ListenAndServe(); err != nil {
        log.Println(err)
    }
}

イメージの作成

まずはコンテナの元となるイメージを作成します。
Dockerfile という名前のファイル(拡張子なし)を作成して、下記を記述してください。

FROM golang:1.9

RUN mkdir /sample
COPY hello.go /sample

CMD [ "go", "run", "/sample/hello.go" ]

イメージは「コンテナを作るための設定や情報が入ったもの」と前述しましたが、その設定などがDockerfileに入っています。
一行ずつ説明していくと、

FROM golang:1.9

のFROMは、作りたいイメージのベースとなるイメージを設定しています。
今回はGoを動かす環境を作りたいため、golangというイメージを指定しています。
このイメージは、Docker Hubというところからダウンロードされます(Dockerイメージの上がっているGitHubみたいなものです)。

:の後の数字はは「タグ」と言われるもので、Gitのタグと同じようなものと考えて良いと思います。
今回は1.9のバージョンのタグを使います。

RUN mkdir /sample
COPY hello.go /sample

RUNはDockerイメージのビルド時に実行される処理です。
サンプルプログラムを配置するための「sample」というディレクトリを作成しています。

そして、COPYはホスト(ビルドを実行している端末)にあるファイルをコンテナにコピーします。
ここではビルドを実行しているディレクトリ内にある「hello.go」というファイルを先ほどRUNで作成した「sample」というコンテナ内のディレクトリにコピーします。

CMD [ "go", "run", "/sample/hello.go" ]

CMDは、Dockerコンテナを起動する時に、コンテナ内で実行する処理になります。
ここでは起動時に先ほどコピーしておいた「hello.go」を実行しています。
CMDの書き方は少し特殊で、このサンプルで言うと

go run /sample/hello.go

をスペースで区切って、分離して配列として書いているような形になります。
そして、このDockerfileを元に、Dockerイメージをビルドします。
下記のコマンドで実行できます。

ntakehata$ docker image build -t ntakehata/hello:latest .

ntakehata/hello:latestの部分はコンテナの名前と、タグの名前(:から後ろ)になります。
.はDockerfileの置いてある場所です。ここで指定したディレクトリからDockerfileという名前のファイルを探して、ビルドを始めます。
下記のように表示されればビルド成功です。

Step 1/4 : FROM golang:1.9
 ---> ef89ef5c42a9
Step 2/4 : RUN mkdir /sample
 ---> Using cache
 ---> 0edc47a2cdb9
Step 3/4 : COPY hello.go /sample
 ---> Using cache
 ---> 250c3984b73f
Step 4/4 : CMD [ "go", "run", "/echo/hello.go" ]
 ---> Using cache
 ---> 3d6091e879fb
Successfully built 3d6091e879fb
Successfully tagged ntakehata/hello:latest

作られたイメージは、下記のコマンドで確認できます。

ntakehata$ docker image ls
REPOSITORY                                 TAG                 IMAGE ID            CREATED             SIZE
ntakehata/hello                            latest              3d6091e879fb        5 minutes ago       750MB

このイメージを使うことで、

①/sampleというディレクトリを作成 ②hello.goを/sampleにコピー ③hello.goを実行

した状態のコンテナを作ることができます。
前述したようにあくまでこれはイメージで、これだけでは何も動かないので、次は実際にコンテナを作ってアクセスしてみます。

Dockerコンテナの実行

下記の docker container run というコマンドで、コンテナを起動できます。

ntakehata$ docker container run -d --name hello ntakehata/hello:latest
8d6565e76f20aefdb5e171dd21aa7e1e6c04ac885a0eb25880de5687387c5098

-d はバックグラウンド実行、 --name 作成するコンテナ名を指定します。
コンテナ名は省略した場合は、自動でランダムな名前が付けられます。

そして、最後に先ほど作成した ntakehata/hello:latest というイメージ名を指定しています。
これで、「ntakehata/hello:latestのイメージを元にhelloというコンテナを作り、バックグラウンドで実行する」ことができます。

作られたコンテナは docker container ls というコマンドで確認できます。

ntakehata$ docker container ls
CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS              PORTS               NAMES
8d6565e76f20        ntakehata/hello:latest    "go run /sample/hell…"   5 seconds ago       Up 4 seconds                            hello

このコンテナは、前述の

①/sampleというディレクトリを作成
②hello.goを/sampleにコピー
③hello.goを実行

が実行され、hello.goが起動した状態のコンテナが自分のPCの中に作られたイメージです。
ただ、コンテナは同じPC内にありながらも、あくまでも別の環境(仮装環境)という形なので、ローカル環境のように http://localhost:8080 にアクセスしても接続できません。
このアクセスをする方法を、これから説明します。

Dockerコンテナの停止

その前に、先ほど起動したコンテナを一度停止しましょう。
作ったコンテナの停止は、 docker container stop というコマンドで、コンテナIDかコンテナ名を指定します。

ntakehata$ docker container stop 8d6565e76f20
8d6565e76f20

下記はコンテナ名で実行した例です。

docker container stop hello

ポートフォワード

さて、それではコンテナ上で起動したhello.goで立てたサーバーにアクセスする方法です。
これには「ポートフォワード」という機能を使います。 下記のコマンドで、もう一度コンテナを起動してください。

ntakehata$ docker container run -d -p 9000:8080 --name hello ntakehata/hello:latest
d8b850dd4e7013ffc8af015d493c832b798c6189273f47459ced72755c4d84fb

-p 9000:8080 というオプションを追加しています。
これは「ポートフォワード」という機能を使うためのもので、この例で言うとローカル環境の9000ポートを経由して、コンテナhelloの8080へアクセスできるようになります。
: の前が受け付けるローカル環境のポート、後が流したい先のコンテナのポートです。

下記のcurlコマンドで9000ポートにアクセスすると、コンテナ上のhello.goにアクセスできているのが分かります。

ntakehata$ curl http://localhost:9000/
Hello World!!

DockerイメージのPush

Dockerには、Docker Hubというイメージを置いて公開できるGitHubのようなサイトがあります。
こちらは必須ではないですが、もしこちらに置きたい場合は、下記のコマンドでPushできます。

ntakehata$ docker image push ntakehata/hello:latest
The push refers to repository [docker.io/ntakehata/hello]

Dockerfileで指定するイメージも、こちらに置いてあるものを指定することができます(先ほどのgolangのイメージも置いてあります)。
ちなみにこのサンプルで置いている「ntakehata/hello:latest」というイメージもDocker HubにPushしてあるので、良ければ使ってみてください。

Docker Composeで複数コンテナの起動

ここまででhello.goの起動まではできました。
ただ先ほどのやり方では1コンテナずつの起動しかできないので、今度は複数コンテナを同時に起動してみます。
実際の環境構築はプログラムだけでなく、nginxやMySQLといったミドルウェアも一緒に必要になってくるのでこちらの方法を使います。

複数コンテナの起動には、Docker Composeというものを使います。
Docker Composeは簡単に言うと、 docker-compose.yml という設定ファイルを書き、その内容に則ってコンテナを起動するものです。

まずは単一のコンテナの起動を試してみる

下記の内容で、docker-compose.ymlという名前のファイルを作ってください。

version: "3"
services:
  hello:
    image: ntakehata/hello:latest
    ports:
      - 9000:8080

この設定の説明ですが、まず version で指定しているのは使用するDocker Composeのバージョンです。
そして、 services の下に作成するコンテナの設定を書いています。
今回は前述のコマンドラインで起動してたのと同じく、helloという名前で作成します。
helloの下の階層で書いている imageports は、それぞれコンテナの元になるイメージ、ポートフォワードの設定を記述しています。

そしてファイルのあるディレクトリで、下記の docker-compose コマンドを実行します。

ntakehata$ docker-compose up -d
Creating network "docker_default" with the default driver
Creating docker_hello_1 ... done

これで先ほどコマンドで起動していたコマンドと同じものが立ち上がっています。

ntakehata$ docker container ls
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS              PORTS                      NAMES
ff82f298d6d6        ntakehata/hello:latest   "go run /sample/hell…"   12 seconds ago      Up 11 seconds       0.0.0.0:9000->8080/tcp     docker_hello_1

ちなみに名前だけ「docker」が付いたりと少し変わりますが、先ほどのコマンドラインで作成したのと同じコンテナが作られています。
Docker Composeで作成したコンテナの削除は、下記で実行します。

ntakehata$ docker-compose down
Stopping docker_hello_1 ... done
Removing docker_hello_1 ... done
Removing network docker_default

複数コンテナを作成してみる

今度は本題で、複数コンテナを一気に作成します。
docker-compose.ymlを下記のように変更してください。

version: "3"
services:
  hello:
    image: ntakehata/hello:latest
    ports:
      - 9000:8080
  nginx:
    image: nginx:1.13.5-alpine
    ports:
      - 80:80

servicesの下に「nginx」という名前のコンテナを追加しました。
指定しているイメージはDocker Hub上にあるnginxのコンテナを作るためのイメージで、ここではローカルの80番ポートからコンテナ内の80番ポート(nginxのデフォルトポート)にポートフォワードしています。

このように、servicesの下にコンテナの設定を羅列していくことで、複数のコンテナを同時に作成、起動することができます。
作成は同じように、 docker-compose コマンドで実行します。
(初回作成時はnginxのイメージのPullが走ると思います)

ntakehata$ docker-compose up -d
Creating network "docker_default" with the default driver
Creating docker_nginx_1 ... done
Creating docker_hello_1 ... done

helloとnginx、2つのコンテナが起動しました。
下記でhelloの動作確認。

ntakehata$ curl http://localhost:9000
Hello World!!

そして下記で80番ポートにアクセスすると、nginxのデフォルトページが表示されると思います。

ntakehata$ curl http://localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

ブラウザで http://localhost にアクセスしても、確認できると思います。

次はKubernetes

とりあえずDockerで基本的な環境を作るところまでできました。
次はこれと同等の環境をKubernetesで作っていきます。