Docker

クジラに乗って海に出た

More than 3 years have passed since last update.

久しぶりの投稿になりました, かどわき(ゲートサイド)です

今までとは状況が変わったので, 気持ち新たに記事を書いていこうと思います!


はじめに

この記事では, Dockerを使ってインフラ, サーバサイド, フロントエンドを全て自分で用意し, Webサービス開発を始める環境を整備した経緯についてご紹介します.

タイトルは

   クジラに乗って(Dockerを使って)

   海に出た(Webサービス開発を始めた)

的な意味でつけました!!!!

変なタイトルつければ記事の流入増えるってばあちゃんが言ってたんですね!!!!

主な記載事項はDockerに偏っちゃうかもですが, Webサービス開発を始める上での題材として, 以下の様な人に役立つ記事にしたいと思います.


  • Dockerの基本を知りたい人

  • Webサービスの開発環境および動作環境を用意したい人

  • フルスタックに興味がある人

多岐にわたるレイヤーの話が出てくるので, 上記以外の人にもお役に立てればと思います.


この記事のゴール


  1. インフラ, サーバサイド, フロントエンドの構成を大体理解できる

  2. コマンド一発でサービス(今回は動作環境)をデプロイできる


使用した言語など


インフラ


  • OS: Linux2.6

  • コンテナ管理: Docker

  • DB: MySQL

  • Cacheサーバ: Redis

  • HTTPサーバ & リバースプロキシ: Apache


サーバサイド


  • 言語: Java

  • フレームワーク: SpringBoot

  • Webサーバ(組込): Tomcat


フロントエンド


  • 言語: Javascript (ES6)

  • モジュールバンドラ: Webpack

  • UIライブラリ: React

  • 状態管理フレームワーク: Redux


ローカル開発環境

Mac OS X 10.11.4 (El Capitan)


サービスディレクトリ構成

MyApp

├── MyApp-client ・・・ フロントエンド
│   ├── node_modules
│   ├── package.json
│   ├── src
│   └── webpack.config.js
├── MyApp-server ・・・ サーバサイド
│   ├── Dockerfile
│   ├── build
│   ├── build.gradle
│   ├── build.sh
│   ├── gradle
│   ├── gradlew
│   ├── gradlew.bat
│   ├── run.sh
│   └── src
├── README.md
├── apache ・・・ HTTPサーバ
│   ├── Dockerfile
│   ├── bash.sh
│   ├── bundle.js
│   ├── httpd.conf
│   ├── index.html
│   └── run.sh
├── datastore ・・・ データストア
│   ├── Dockerfile
│   ├── build.sh
│   └── run.sh
├── db ・・・ データベース
│   ├── Dockerfile
│   ├── build.sh
│   ├── charset.cnf
│   └── run.sh
├── docker-compose.yml
├── redis ・・・ キャッシュサーバ
│   └── Dockerfile
├── remove_all_images.sh
├── remove_containers.sh
└── stop_containers.sh


ソースコード

https://github.com/gates1de/MyApp


準備


インストール


homebrew-caskを利用する方法

(homebre-caskは便利なので入れとくといいかも)

$ brew install caskroom/cask/brew-cask

$ brew cask install dockertoolbox

# docker, docker-machine, docker-compose, virtualbox, kitematic(GUI tool)が入ります


homebrewのみでやる方法

$ brew install Caskroom/cask/dockertoolbox

# 同様に docker, docker-machine, docker-compose, virtualboxが入ります
# もしvirtualboxが入っていた場合は以下を実行

$ brew install docker docker-machine docker-compose


パッケージを利用する方法

https://www.docker.com/products/docker-toolbox


インフラ


Docker

まずはインフラ構築として, Dockerについて説明していきます.

ただし, 今更1から説明するのは冗長的なので, この方の記事

が参考になるかと思います.

このQiitaにも既に2600を超える記事に「Docker」タグがついていて(2016/06/02時点), それなりに利用されていることが伺えます.


メリット

自分が感じている主なメリットはこんな感じです.

* 用意したい環境をすぐに構築できる(例: Nginxを利用したHTTPサーバ, Rails + unicorn, キャッシュサーバなど)

* インフラ構築をコードで自動化できる → 環境によってアプリケーションが動かないということがなくなる

* コンピュータのリソースを隔離・制限できる → 少ないリソースでアプリケーションを動かす事ができる

* オーバーヘッドが少なく, インスタンスの作成や起動が早い


など


コマンド要約

今回の記事で使うのは以下3つのコマンドのみです

$ docker : コンテナを操作するコマンド

$ docker-machine : コンテナを動かすための仮想マシン(VM)を操作するコマンド

$ docker-compose : 複数のコンテナをまとめて操作するコマンド


Docker Machine

まずはこれでVM作りましょう!

$ docker-machine create --driver virtualbox MyAppVM

はい終わり!!!

というと不安かと思いますので,

$ docker-machine ssh MyAppVM

とか実行してみてください.

普段サーバにsshするのと同じようにVMで操作できるでしょう!(ログアウトするときは同様に ctrl + dで)

今後のコンテナ操作のために以下を実行しておいてください.


  • $ eval $(docker-machine env MyAppVM)

    dockerコマンドで利用する環境変数が設定され, コンテナの管理先VMが自動的に認識されます.


  • $ docker create network myapp-net

    あらかじめコンテナ同士を繋ぐネットワークを作っておきます.



Dockerfile

Dockerfileはコンテナの構成内容を記述するファイルであり, この内容を元にdocker buildをおこなうと, 思い通りのコンテナイメージが出来上がります.

今回は, 各コンテナの設定ファイルで簡単に説明します.


serverコンテナイメージのDockerfile


/myapp/myapp-server/Dockerfile

FROM java:8

ADD build/libs/myapp-0.0.1-SNAPSHOT.jar /opt/tomcat8/webapps/myapp-0.0.1-SNAPSHOT.jar

WORKDIR /opt/tomcat8/webapps

EXPOSE 8080

CMD ["java", "-jar", "myapp-0.0.1-SNAPSHOT.jar"]


命令
引数

FROM
元となるイメージの指定

ADD
ファイル・ディレクトリの追加

WORKDIR
作業ディレクトリの指定

EXPOSE
他コンテナに晒すポートの指定

CMD
コンテナの作成後に実行するコマンド

上記の設定をするようなケースを, ストーリーで説明してみます.


  • SpringBootのWebアプリが動く環境が欲しいなぁ

  • javaさえ入ってれば動くんだけどなぁ

  • どうやらjavaのイメージがあればjavaが動くらしいぞ

  • じゃあjava:8というイメージを元にコンテナを作成して, この中にSpringBootのWebアプリ(jarファイル)を置くぞ

  • コンテナの◯◯ディレクトリにjarファイルを追加することにしよう

  • Webアプリの起動コマンドを実行する場所はjarファイルの置いてある場所にしておこう

  • 後でapacheから繋げるように, 8080ポートは晒しとくのだ


  • $ docker build -t spring-boot . ッターン

という感じで, そのWebアプリが動く環境のイメージを元にしてファイル追加や起動コマンドなどの命令をDockerfileに書き込んでおくと, 思い通りのコンテナが出来るわけです.

java:8イメージって何や!(# ゚Д゚)」って人には申し訳ない説明になりましたが, イメージは自分で作成する他, DockerHubというDocker版Githubみたいなサービスからpullしてくることができます.

今回使ったイメージは, DockerHubのjava公式イメージになります. FROMで指定したイメージは, 手元になければDockerHubから探して勝手にとってきます. それでもなければエラーになるでしょう.

他に以下4つのコンテナを作成するためのDockerfileを記載します

* HTTPサーバ(Apache)

* DBサーバ(MySQL)

* キャッシュサーバ(Redis)

* データストア


HTTPサーバ


/myapp/apache/Dockerfile

FROM httpd

RUN mkdir -p /usr/local/apache2/htdocs/myapp/build

COPY ./httpd.conf /usr/local/apache2/conf/httpd.conf
COPY ./index.html /usr/local/apache2/htdocs/myapp/index.html
COPY ./bundle.js /usr/local/apache2/htdocs/myapp/build/bundle.js

EXPOSE 80


少しだけ解説すると, RUNはコンテナの作成前に実行される命令で, いくつでも実行できる模様. CMDと似ているが, CMDはコンテナ作成後に実行される命令で, 1回のみ実行できる模様.

この変ややこしいのですが, 基本RUNでディレクトリを作成したりライブラリをインストールしたりしてから, CMDでコンテナ内部で動かしたいツールやソフトの実行命令を指定するのが良さそうです.

他にもENTRYPOINTとかあるけど今回はいっかなぁ

COPYはその通り, 指定元ファイルを指定先ファイルへコピーする命令です.

ADDはファイルを追加しつつzipファイルとか展開してくれるのと, リモートからファイルも追加できるらしいので, ADDの方が多機能っぽいです. でもCOPYですむならCOPYを使えばいいと思います.

ちなみに, index.htmlbundle.jsはフロントエンドの開発で生成したファイルです.

Webpackを使うとjscssを, よしなに処理してひとまとめにしてくれます.


DBサーバ


/myapp/db/Dockerfile

FROM mysql

COPY charset.cnf /etc/mysql/conf.d/charset.cnf

EXPOSE 3306


公式のMySQLイメージ利用したのですが, 日本語環境がアレなのでcharset.cnfを追加しました.

こんな感じ.


charset.cnf

[mysqld]

character-set-server=utf8
[client]
default-character-set=utf8


キャッシュサーバ


/myapp/redis/Dockerfile

FROM redis

EXPOSE 6379


特に使わなかったので公式イメージそのままコンテナにするという暴挙


データストア


/myapp/datastore/Dockerfile

FROM busybox

VOLUME /var/lib/mysql


MySQLのデータを永続化するために, 必要最低限のLinuxコマンドだけ入ってるコンテナを作って, ホストマシンの特定のディレクトリをボリュームとしてマウントする役割を与えました.

そうしておけば, サービスのコンテナを全部潰してもデータは消えずにすむはずです.

ログファイルなどもこのデータストアコンテナを利用して保存するのが良いようです.

以上5つのコンテナでWebサービスを構成します.


Docker Compose

今回の中心になりうるツールの1つで, これがあることでコマンド1発デプロイが実現できるのです.

何故かと言うと, この設定ファイルで上記のDockerfileを元にコンテナの一斉ビルドと起動をおこなえるようになるからです.

自分が説明するより 'docker compose' と検索して一番上に出てくるこの記事が非常にわかりやすそうです.

docker composeでは, docker-compose.ymlというファイルを作成してコンテナの構成管理を設定します.

以下, 今回の記事で利用するdocker-compose.ymlの内容とざっくりとした解説をおこないます.


/myapp/docker-compose.yml

version: '2'   # ① 

services: # ②
server: # ③
container_name: spring-boot # ④
build: ./myapp-server # ⑤
ports: # ⑥
- '8080:8080'
environment: # ⑦
REDIS_URL: redis://redis:6379
links: # ⑧
- db
- redis
network_mode: 'myapp-net' # ⑧
db:
container_name: mysql
build: ./db
environment: # ⑨
MYSQL_DATABASE: myapp_db
MYSQL_ROOT_PASSWORD: root
MYSQL_USER: myapp
MYSQL_PASSWORD: password
ports:
- '3306:3306'
volumes_from: # ⑩
- datastore
network_mode: 'myapp-net'
datastore:
container_name: datastore
build: ./datastore
network_mode: 'myapp-net'
redis:
container_name: redis
build: ./redis
ports:
- '6379:6379'
network_mode: 'myapp-net'
apache:
container_name: apache
build: ./apache
ports:
- '80:80'
links:
- server
network_mode: 'myapp-net'

番号
key
解説


version
ymlのバージョン(サービスのバージョンじゃないです)


services
トップレベルオブジェクトの1つで, この階層に各コンテナの設定情報を書いていきます.
トップレベルオブジェクトには他にvolumesnetworksが定義できます(今回は省略).


server
ここではserverとしていますがkeyは何でもよいです.
ただし, Webサービスとして何の役割を持つコンテナなのかが分かるkeyにしたほうが良いでしょう.

上記の例で言えば, docker-compose.yml が置いてあるディレクトリの名前が MyApp , keyが server なので, 作成されるコンテナのイメージ名が myapp_server となります.
これなら, MyAppサービスのサーバサイドコンテナだと一目で分かるかと思います.


container_name
そのままの意味でコンテナ名となります. 人にもよりますが, 主にそのコンテナで作動しているアプリやソフト, ツールの名前にしてます.
(spring-boot, apache, mysqlなど)


build
Dockerfileを元に, コンテナをビルドするための設定です. Dockerfileが置いてあるディレクトリを指定します.


ports
公開するポートを指定します. ハッシュのvalueとしてシーケンス(配列形式)を指定するため, 以下のように複数のポートを公開することが可能です.
ports:
- "80":"80"
- "8080" : "8080"


environment
コンテナ内部の環境変数を追加する設定です.


links
他のコンテナとのリンク設定です. これを設定しておくと, コンテナ名がホストネームとなり, 名前解決が出来るようになります.
上の例で言うと, spring-bootコンテナとdbコンテナにはIPアドレスが自動的に振られているので(172.20.0.4172.20.0.3), コンテナが起動した後にしかDB接続先がわからず, わざわざspring-bootコンテナでDB接続設定を書き換えなければなりません.
ダルい上に今回はspring-bootをjarファイルとして動かしていることもあり, ソースコードは書き換えられません.
そこでlinksを使うと, mysqlという名前のホストネームをspring-bootのDB接続設定に書いておくことで, 自動的にDBの接続先は172.20.0.3であると判断してくれます.


environment
コンテナ内の環境変数を設定します.
今回のケースではMySQLの環境変数を正しく設定すると, 自動的にDBとユーザを作成してくれます.
この辺の環境変数設定はコンテナによりけりな印象なので, DockerHubの公式イメージを見てみると良いかもしれません.


network_mode
作成したコンテナ間ネットワークの設定です.
基本的にサービス全体を1つのネットワークで動かす想定なので, コンテナを同一ネットワークで繋ぐために全てのコンテナ設定でこの記述を追加します.
複数追加できるので, 別のネットワークを作って繋ぎたいコンテナ同士を繋ぐのも良いでしょう. ※1

そしてついに! ここであのコマンドを打つのです

$ docker-compose up

必要なイメージをすべてpullしてきて, Dockerfileの設定を元に全コンテナをビルド&起動します.

これでサーバサイド・フロントエンド・HTTPサーバ・DBサーバ・キャッシュサーバがDockerで作られたインフラ上で動作し, 同一ネットワークで繋がれたサービスが出来上がるのです.

↑の方にGithubのリポジトリへのリンクを貼っているので, dockerやらdocker-machineやらをいれたなら, READMEGet Started通りにやってみてください!

多分動くと思います(動かなかったらコメントにお願いします!!)

※1

docker-composeでコンテナを立ち上げる時に, 今回のようにこだわりなく全て同じネットワークで繋ぎたい場合は, わざわざ$ docker create network hogehogeでネットワークを作ってnetwork_modeを全てに指定しなくても, デフォルトでネットワークが作成されます.

リポジトリに上がっているものに関してはnetwork_modeを削除しています.


おわりに

ちょっと文章が多くなってしまったのと, 省略してしまったところは申し訳ないですorz

とにかく, Dockerはどんなエンジニアにとっても便利なツールになるはずです.

モバイルエンジニアも簡単なAPI作って連携してみたり, 学生エンジニアで一つのレイヤーを専門にしてしまっている人でもWebサービス全体を学べる環境が作れたり, 幅広く役に立ちます.

また, 会社で業務をおこなっている人でも新規サービスを提案するにあたり, モックアプリなどを何人かで爆速開発したいと思った時に, この環境さえあればレイヤーの切り分けを簡単にできて, デプロイも簡単にできるはずです.

使って損はないと思うので是非試してみてください! ♪〜(´ε` )

また, この後数週間にわたってこの記事の中で実装したフロントエンドやらサーバサイドやらについて書くつもりです〜

それでは長文失礼致しました!


p.s. ここが分からない!といったことがあれば質問受け付けてます!!


子記事


  1. Spring Bootで作ったRESTful APIをコンテナにして動かす