---
slideOptions:
theme: white
slideNumber: 'c/t'
center: false
transition: 'none'
keyboard: true
width: '93%'
height: '100%'
---
<style>
/* basic design */
.reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6,
.reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p {
font-family: 'Meiryo UI', 'Source Sans Pro', Helvetica, sans-serif, 'Helvetica Neue', 'Helvetica', 'Arial', 'Hiragino Sans', 'ヒラギノ角ゴシック', YuGothic, 'Yu Gothic';
text-align: left;
line-height: 1.8;
letter-spacing: normal;
text-shadow: none;
word-wrap: break-word;
color: #444;
}
.reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6 {font-weight: bold;}
.reveal h1, .reveal h2, .reveal h3 {color: #2980b9;}
.reveal th {background: #DDD;}
.reveal section img {background:none; border:none; box-shadow:none; max-width: 95%; max-height: 95%;}
.reveal blockquote {width: 90%; padding: 0.5vw 3.0vw;}
.reveal table {margin: 1.0vw auto;}
.reveal code {line-height: 1.2;}
.reveal p, .reveal li {padding: 0vw; margin: 0vw;}
.reveal .box {margin: -0.5vw 1.5vw 2.0vw -1.5vw; padding: 0.5vw 1.5vw 0.5vw 1.5vw; background: #EEE; border-radius: 1.5vw;}
/* table design */
.reveal table {background: #f5f5f5;}
.reveal th {background: #444; color: #fff;}
.reveal td {position: relative; transition: all 300ms;}
.reveal tbody:hover td { color: transparent; text-shadow: 0 0 3px #aaa;}
.reveal tbody:hover tr:hover td {color: #444; text-shadow: 0 1px 0 #fff;}
/* blockquote design */
.reveal blockquote {
width: 90%;
padding: 0.5vw 0 0.5vw 6.0vw;
font-style: italic;
background: #f5f5f5;
}
.reveal blockquote:before{
position: absolute;
top: 0.1vw;
left: 1vw;
content: "\f10d";
font-family: FontAwesome;
color: #2980b9;
font-size: 3.0vw;
}
/* font size */
.reveal h1 {font-size: 5.0vw;}
.reveal h2 {font-size: 4.0vw;}
.reveal h3 {font-size: 2.8vw;}
.reveal h4 {font-size: 2.6vw;}
.reveal h5 {font-size: 2.4vw;}
.reveal h6 {font-size: 2.2vw;}
.reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p {font-size: 2.2vw;}
.reveal code {font-size: 1.6vw;}
/* new color */
.red {color: #EE6557;}
.blue {color: #16A6B6;}
/* split slide */
#right {left: -18.33%; text-align: left; float: left; width: 50%; z-index: -10;}
#left {left: 31.25%; text-align: left; float: left; width: 50%; z-index: -10;}
</style>
<style>
/* specific design */
.reveal h1 {
margin: 0% -100%;
padding: 2% 100% 4% 100%;
color: #fff;
background: #c2e59c; /* fallback for old browsers */
background: linear-gradient(-45deg, #EE7752, #E73C7E, #23A6D5, #23D5AB);
background-size: 200% 200%;
animation: Gradient 60s ease infinite;
}
@keyframes Gradient {
0% {background-position: 0% 50%}
50% {background-position: 100% 50%}
100% {background-position: 0% 50%}
}
.reveal h2 {
text-align: center;
margin: -5% -50% 2% -50%;
padding: 4% 10% 1% 10%;
color: #fff;
background: #c2e59c; /* fallback for old browsers */
background: -webkit-linear-gradient(to right, #64b3f4, #c2e59c); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to right, #64b3f4, #c2e59c); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
</style>
<!-- --------------------------------------------------------------------------------------- -->
#### Docker入門
# Docker Compose でコンテナをええ感じに起動しよう 【nginx & go & mysql】
<br>
<br>
#### 2019/08/11
---
## 今回のゴール
<div class="box">
- Docker Composeを触って知ってもらう
- 複数のコンテナを組み合わせることを知ってもらう
- Dockerを更に学ぶきっかけになる
</div>
---
## 今回やること
#### やること
<div class="box">
- docker-machineを使ってGoogle Cloud PlatformにVMインスタンスを立てる
- docker-composeのインストール
- 複数のDockerコンテナのビルド・起動
- mysql・REST API(go)・リバースプロキシ(nginx)
</div>
---
## 準備するもの
- 自分のPC
- これが無いと何もできない
- Google Cloud Platformアカウント設定
- https://console.cloud.google.com
- 初回登録の無料枠で十分まかなえます
- 無料枠がない場合は今回のスペックなら
2019/07/19時点で1時間あたり約$0.7です
- shell・curl・git等のある程度の知識
---
## 前回のおさらい
#### Dockerとはなにか?
大きく分けてイメージとコンテナの2種類あるよ
- 「Docker イメージ」とはコンテナを起動するためのベース
- read only
- イメージレイヤー(gitでいうコミット)を重ねて作った成果物
- 「Docker コンテナ」とは「Docker イメージ」を元にプログラムを実行できるようにした物
- Dockerイメージの上に書き込み可能レイヤーが乗っかっている
- 書き込み可能レイヤー上でプログラムを実行できる
---
## Docker Composeとは
<div class="box">
**[Docker Compose](https://docs.docker.com/compose/)** は、複数のコンテナで構成されるアプリケーションについて、Dockerイメージのビルドや各コンテナの起動・停止などをより簡単に行えるようにするツールです。
Docker Composeでは、Dockerビルドやコンテナ起動のオプションなどを含め、複数のコンテナの定義をymlファイルに書き、それを利用してDockerビルドやコンテナ起動をすることができます。
一つの簡単なコマンドで複数のコンテナを管理できるようになります。
</div>
:::info
今回は実際に簡単なアプリケーションを構築しながら解説していきます
:::
---
## Docker環境構築 (GCP)
<div style="text-align: center;">

</div>
---
## GCP上にDocker環境を簡単に構築 (1)
#### 今回はCloud Shellから構築
コンソール ウィンドウの上部にある **[Cloud Shell をアクティブにする]** ボタンをクリックします。

コンソールの下部の新しいフレーム内で Cloud Shell セッションが開き、コマンドライン プロンプトが表示されます。シェル セッションが初期化されるまで、数秒かかる場合があります。

---
## GCP上にDocker環境を簡単に構築 (2)
#### コマンド
```bash=
# VMインスタンスを作成するGCPのプロジェクトを設定する
GOOGLE_PROJECT="****"
```
```bash=+
# マシンイメージのURL設定
MACHINE_IMAGE="https://www.googleapis.com/compute/v1/projects/centos-cloud/global/images/centos-7-v20190619"
```
```bash=+
# VMインスタンス名
VM_NAME="docker-test"
```
```bash=+
# VMインスタンスの作成
docker-machine create --driver google \
--google-project ${GOOGLE_PROJECT} \
--google-zone asia-northeast2-b \
--google-machine-type n1-standard-1 \
--google-machine-image ${MACHINE_IMAGE} \
${VM_NAME}
```
---
## GCP上にDocker環境を簡単に構築 (3)
#### GCP上のVMインスタンスにSSH
1. GCP Console の [VM インスタンス] ページに移動します。
[[VMインスタンス] ページに移動](https://console.cloud.google.com/compute/instances?hl=ja&_ga=2.158058162.-962716978.1548834422)
2. 仮想マシン インスタンスのリストで、接続するインスタンスの行の SSH をクリックします。

---
## Docker Compose インストール
#### コマンド
```bash=
sudo curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
```
---
## Docker Compose が使えるか確認
#### コマンド
```bash=
docker-compose --version
```
こういう結果が出ればOK
```bash
docker-compose version 1.24.1, build 4667896b
```
---
## Gitコマンド インストール
CentOS 7のパッケージでインストールされるgitのバージョンが古すぎるためソースからビルドしてインストールします。
#### コマンド
```bash=
# ビルドに必要なパッケージのインストール
sudo yum -y install gcc curl-devel expat-devel gettext-devel openssl-devel zlib-devel perl-ExtUtils-MakeMaker autoconf
```
```bash=+
# ダウンロード&展開
cd /usr/local/src
sudo wget https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.22.0.tar.gz
sudo tar xzvf git-2.22.0.tar.gz && sudo rm git-2.22.0.tar.gz
```
```bash=+
# コンパイル&インストール
cd /usr/local/src/git-2.22.0
sudo make prefix=/usr/local all && sudo make prefix=/usr/local install
git --version
```
:::success
git version 2.22.0
:::
---
## 練習用Git リポジトリ clone
- REST APIを介してユーザー情報をDBに登録するサンプルです。
- MySQL 5.7, Golang, Nginxで構成してます
- nginxは簡単に言うとApacheのようなOSSWebサーバーです
練習用のファイルを落としてきます。
#### コマンド
```bash=
git clone https://github.com/hy-yoneda/docker-compose-hands-on.git
cd docker-compose-hands-on
````
:::warning
これ以降は「**docker-compose-hands-on**」ディレクトリで作業します
:::
---
## リポジトリの中身
```yaml
app: # app: アプリケーションのコード(golang)
users:
users.go # app: ユーザー情報をDBに書き込むモジュール
main.go # app: goのmainファイル, APIのセットアップ用
```
```yaml
docker: # docker: Dockerビルド用のファイル
mysql: # db: mysql 5.7
init:
create_db.sql # db: テーブル作成クエリ
my.conf
nginx: # proxy: nginx
default.conf
```
```yaml
docker-compose.yml # app, db, proxyコンテナを作るためのファイル
Dockerfile # app: Dockerfile
```
---
## Dockerだけでビルド
#### Dockerfileの確認
```bash=
find . -type f -name Dockerfile
```
```bash
./Dockerfile
```
#### これらを普通にビルドすると...
```bash=
docker build -t docker-compose-hands-on_app ./
```
:::info
様々な工夫によりビルドは1回だけです
:::
---
## Dockerだけで起動
#### ただし起動するには...
- ネットワークの作成
- ボリュームの作成
- コンテナの起動
- ポートの割り当て
:::warning
かなりやることが多い/(\^o\^)\
:::
---
## Dockerだけで管理する場合の不便さ
<div id="left">

</div>
<div id="right">
- 今回の場合、dockerで3つのコンテナを立てる
- DB(MySQL)
- REST API(go)
- リバースプロキシ(nginx)
- コンテナ起動順序も意識する必要がある
- docker runのlinkオプションを使用するため
- 左図の用な感じ
- 起動スクリプトが必須
- シェルスクリプトなど
</div>
---
## Docker Composeでビルドする
#### docker-composeだと
```bash=
docker-compose build
```
#### docker-compose.yml に定義されている内容が処理され...
```bash
Successfully tagged docker-compose-hands-on_app:latest
```
:::success
すべての定義のビルドが完了します
今回は1つしかありませんが、もちろんDockerfileが増えた時もビルドを行ってくれます
:::
<br>
:::info
Docker Composeは**docker-compose.yml**を元に、**複数のイメージのビルドやコンテナ**のDockerコマンドを**指定したパラメータ**で実行することができます。
:::
---
## Docker Composeでビルドする (確認)
イメージを確認すると**docker-compose-hands-on_app**ができているはずです (他の2つができていない理由はあとで説明します)
```bash=
docker images
```
```bash
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-compose-hands-on_app latest bac6852c4101 9 minutes ago 17.9MB
```
---
## docker-compose.yml について
#### docker-compose.yml
```yaml=
version: '3.4'
services:
db:
image: mysql/mysql-server:5.7 # Dockerfileを使わず、使用するイメージを直接指定
environment: # 起動時の環境変数
MYSQL_ROOT_PASSWORD: test
MYSQL_USER: test
MYSQL_PASSWORD: test
MYSQL_DATABASE: test
volumes:
- "db-data:/var/lib/mysql" # DBデータの永続化
- "./docker/mysql/my.conf:/etc/mysql/my.conf" # 初期設定を読み込む
- "./docker/mysql/init/:/docker-entrypoint-initdb.d" # MySQL起動時にDBを作成する
app:
build:
context: .
args: # ビルド時の環境変数
GOOS: linux
GOARCH: amd64
depends_on:
- db
proxy:
image: nginx:alpine # Dockerfileを使わず、使用するイメージを直接指定
ports:
- 80:80
volumes:
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- app
volumes:
db-data: # ここで作成したボリュームがDBで使用されることによってデータが永続化される
driver: local
```
---
## docker-compose.yml について (解説 1)
#### 解説
- 「**services**」の下に「db」と「app」と「proxy」とありますが、これがビルドするイメージまたは起動するコンテナに相当すると考えてください。
- 「**build**」にはDockerビルドするディレクトリや引数を渡すことができます
- 「build」を行わずに「**image**」でリポジトリ上のイメージを指定することもできます
- 「**depends_on**」はコンテナの起動順序や依存関係を指定できる
- ただし、コンテナの起動は完了してもプロセスの初期化処理までは待機しないので注意が必要
- 似たようなパラメータで「**links**」がありますがこちらは**非推奨**になっています
- 「**docker-compose build**」するとディレクトリ名がプロジェクト名として扱われる
- イメージ/コンテナ名が『ディレクトリ名_各サービス名』になるため命名に注意
---
## docker-compose.yml について (解説 2)
#### 解説
- 「**volumes**」は今回2種類の設定がされています
- 「Data Volume」を作成してDocker上のリソースとして扱っているものをマウントする
- ローカルマシンから直接中身を参照することはできない
- ※「**Docker Compose バージョン2**」には「**Data Volume Container**」が使われてましたが**廃止予定**なので**上の方法で覚えた方が良いです**
- ローカルマシンのファイルシステムをbindマウントする
- ローカルマシンからも直接中身を参照することができる
- 一度コンテナにファイルをコピーするため時間がかかる
---
## Docker Composeで起動する
#### コマンド
```bash=
docker-compose up -d
```
```bash
Creating network "docker-compose-hands-on_default" with the default driver
Creating volume "docker-compose-hands-on_db-data" with local driver
Pulling db (mysql/mysql-server:5.7)...
{省略}...
Status: Downloaded newer image for mysql/mysql-server:5.7
Pulling proxy (nginx:alpine)...
{省略}...
Status: Downloaded newer image for nginx:alpine
Creating docker-compose-hands-on_db_1 ... done
Creating docker-compose-hands-on_app_1 ... done
Creating docker-compose-hands-on_proxy_1 ... done
```
:::success
イメージのダウンロード、ネットワーク・ボリューム・コンテナが作成されました
:::
---
## Docker Composeで起動する (起動確認)
#### **docker-compose ps** してみる
```bash=
docker-compose ps
```
```bash
Name Command State Ports
--------------------------------------------------------------------------------------------
docker-compose-hands-on_app_1 /app_bin Up 9000/tcp
docker-compose-hands-on_db_1 /entrypoint.sh mysqld Up (healthy) 3306/tcp, 33060/tcp
docker-compose-hands-on_proxy_1 nginx -g daemon off; Up 0.0.0.0:80->80/tcp
```
:::success
「State」が「Up」になっていれば、コンテナ起動が完了しています
:::
---
## Docker Composeで起動する (ログ確認)
#### **ログの確認**
```bash=
docker-compose logs
```
```bash
Attaching to docker-compose-hands-on_proxy_1, docker-compose-hands-on_app_1, docker-compose-hands-on_db_1
app_1 | [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
app_1 |
app_1 | [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
app_1 | - using env: export GIN_MODE=release
app_1 | - using code: gin.SetMode(gin.ReleaseMode)
app_1 |
app_1 | [GIN-debug] GET / --> main.main.func1 (3 handlers)
app_1 | [GIN-debug] POST /api/v1/user --> app/users.Register (3 handlers)
app_1 | [GIN-debug] Listening and serving HTTP on :9000
db_1 | [Entrypoint] MySQL Docker Image 5.7.27-1.1.12
db_1 | [Entrypoint] Initializing database
db_1 | [Entrypoint] Database initialized
db_1 | Warning: Unable to load '/usr/share/zoneinfo/iso3166.tab' as time zone. Skipping it.
db_1 | Warning: Unable to load '/usr/share/zoneinfo/leapseconds' as time zone. Skipping it.
db_1 | Warning: Unable to load '/usr/share/zoneinfo/tzdata.zi' as time zone. Skipping it.
db_1 | Warning: Unable to load '/usr/share/zoneinfo/zone.tab' as time zone. Skipping it.
db_1 | Warning: Unable to load '/usr/share/zoneinfo/zone1970.tab' as time zone. Skipping it.
db_1 |
db_1 | [Entrypoint] running /docker-entrypoint-initdb.d/create_db.sql
db_1 |
db_1 |
db_1 | [Entrypoint] Server shut down
db_1 |
db_1 | [Entrypoint] MySQL init process done. Ready for start up.
db_1 |
db_1 | [Entrypoint] Starting MySQL 5.7.27-1.1.12
```
:::info
「-t」でタイムスタンプ、「-f」でログ追従できるので便利
:::
---
## REST APIテスト (1)
#### APIリクエスト
```bash=
# VMホストのIP
VM_HOST_IP="*.*.*.*"
# curl post
curl -X POST "http://${VM_HOST_IP}/api/v1/user?name=hoge&mail=hoge@hoge"
```
:::warning
手元のPCのシェルか、最初に使用した「google cloud shell」上から実行してみましょう
(無理ならVM上で実行しても良いです)
:::
---
## REST APIテスト (2)
#### DBに登録されているか確認
```bash=
# docker-compose exec [サービス名] [コマンド] 引数...
docker-compose exec db mysql -utest -ptest -Dtest -e "SELECT * FROM users;"
```
:::info
execを使うと起動中のコンテナでコマンドを実行できます
:::
---
## 各コンテナの構成
#### 接続図

:::info
名前が違うのは置き換えて解釈して下さい
:::
---
## 各コンテナの構成 (dbコンテナ)
- イメージ ([mysql-server:5.7](https://hub.docker.com/r/mysql/mysql-server))
- Docker公式ではなくOracleが用意してくれている
- Dockerhubで公開されているイメージをそのまま利用
- コンテナ
- 環境変数でユーザーやパスワードの設定
- 「/var/lib/mysql」にボリュームを割り当ててDBのデータを永続化
- 「.conf」はローカルからバインド
- 初期化用SQLを「/docker-entrypoint-initdb.d」にバインドすると自動的に使用するようにイメージ側が実装してくれている
---
## 各コンテナの構成(appコンテナ-イメージ)
- イメージ
- **[Multi Stage Build](https://docs.docker.com/develop/develop-images/multistage-build/)** を使ってビルド用のイメージと実行用のイメージを分離
- ビルダー ([golang:alpine](https://hub.docker.com/_/golang))
- ベースイメージがalpine版のイメージを使用しているためサイズが小さい
- gitがインストールされていないのでパッケージマネージャでインストール
- 「appディレクトリ」をビルドコンテナ側にコピーして、ビルドを行う
- バイナリ実行 ([scratch](https://hub.docker.com/_/scratch))
- busyboxよりも小さいイメージ、というより何も入っていない (shコマンドすら入っていない)
- シンプルにビルダーで**static link**されたバイナリのみが入っている
---
## 各コンテナの構成(appコンテナ-コンテナ)
- コンテナ
- 内部のみで9000版ポートで繋がるようにしている
- dbにはサービス名(db)を指定して接続「test:test@tcp(db:3306)/test」
- 「dbコンテナ」に依存するように設定されている(depends_on)
---
## 各コンテナの構成 (proxyコンテナ)
- イメージ ([nginx:alpine](https://hub.docker.com/_/nginx))
- ベースイメージがalpine版のイメージを使用しているためサイズがかなり小さい
- Dockerhubで公開されているイメージをそのまま利用
- コンテナ
- 外部からの入り口のためポート80を使用
- 内部的に「appコンテナ」のポート9000に流している
- 「appコンテナ」に依存するように設定されている(depends_on)
---
## まとめ
<div class="box">
**Docker Compose** の使い方を説明しました。
複数のコンテナを使う場合は管理がしやすくなり、一つのコンテナでも **オプション設定を何度も入力しなくてよくなります**。Docker Composeを活用して、より効果的にDockerを使っていきましょう。
またコンテナを高速に起動するためには **イメージのサイズを小さくすることが大事** です。
リポジトリで **latestタグで提供されているイメージのベースはUbuntuかDebian等リッチなOSが指定されていることが多い** ので、今回のようにalpineやbusyboxやscratchを使ってサイズの縮小を試みましょう。
</div>
---
## あと片付け
#### 関連する「コンテナ&イメージ&ボリューム」の削除
```bash=
docker-compose down --rmi all --volumes
```
#### 全ての「コンテナ&イメージ&ボリューム」の削除
```bash=
docker system prune -af --volumes
```
#### VMシャットダウン
```bash=
sudo systemctl poweroff
```
:::warning
料金が不安なら **GCPコンソール** でインスタンスを削除するかプロジェクトごと削除して下さい。
:::