なかなか入門できなかった人のためのdocker(+go)入門以前 part1

# まえがき

goのプログラムをdockerで動かしたい、あわよくばk8sでデプロイしたいシリーズ第二弾です。
この part1 では、 `docker run` してdocker上でプログラムを動作させるところまでを扱います。

- 第一弾: [なかなか入門できなかった人のためのgo+ginによるAPI開発入門以前](https://qiita.com/MasashiFujiike/items/bfa784a77c5e22d4a4ce)

---

流行りのdockerですが、なかなか波に乗り切れず、ローカル開発環境の方が簡単じゃない？ と思ってしまっているうちにだいぶ時間が経ってしまいました。

これ以上化石になってしまわないように、なんとかdockerに入門したい。あわよくばgoを動かしたい。そんな記事です。

なお、先々にk8sでのデプロイを目論んでいる関係もあり、この記事ではdocker-compose[^1]を扱いません。

# 想定読者

- dockerよくわからない
- docker使いたい
- 急に複雑な構成が出てきても困る
- オプションとかの細かい説明や「これが最強だ」みたいなハイテクコマンドが出てくると理解を拒否してしまう
- ローカル環境やVagrantでなら非常に初歩的なことで躓いてdocker入門しきれてない
- 環境構築的には、Vagrantfile(そこまで深くはわからんけど)いじったことあるよーぐらいのレベルの人

# あまり想定していない読者
 
- docker-composeの使い方を学びたい
  - docker-composeに限りませんが、しっかり学びたい方はこの記事とかすごいです [いまさらDockerに入門したので分かりやすくまとめます](https://qiita.com/gold-kou/items/44860fbda1a34a001fc1)
- goの最低限の動かし方を知りたい
  - [第一弾](https://qiita.com/MasashiFujiike/items/bfa784a77c5e22d4a4ce)の記事を前提にこの記事では説明を進めるので、記事内でのgoに関する説明はあまりありません
- webサーバーとか仮想環境とかなんのことだかよくわからない、ぐらいのレベルの人
  - ローカルwebサーバーとしてのdockerの立ち上げ方の説明を試みているので、そのあたりの前提になっている知識の説明が少ない可能性があります。
- Windowsの方
  - 筆者がMacを使っていてMacの環境を前提に説明するので、Windowsの方に全然優しくない説明の可能性が高いです

# dockerの特徴について

以下のような特徴があり、旧来の勢力であるローカル開発環境やVagrantのいいとこ取りがしやすいとのことで、近年急激に普及してきています。

- 配布可能な仮想環境
    - DockerfileによってIac(Infrastructure as Code)が実現でき、共通の開発環境を簡単に配布できます
- コンテナ型仮想化
    - コンテナと呼ばれる技術(というか、考え方？)を用いて環境構築をします。
    - イメージとしては、OSをdockerがエミュレート[^2]してくれて、開発に実際に関係ある部分だけ都度環境構築するイメージ
    - 従来型のホスト型仮想化と呼ばれる形式と比べて軽量に動作しやすいです[^3]
- Kubernetes(通称k8s)などのコンテナオーケストレーションサービスとの連携
    - 連携しやすかったです(感想)。GKEでは作ったコンテナをCloud Registryに追加して画面ぽちぽちするだけでデプロイできました。
    - よりスケーラブルな話はまたいつか

以下の記事の方がよりわかりやすかったり、詳しかったりしそうです。

- [Dockerの特徴とメリットについてまとめてみた](https://qiita.com/kasackee/items/aec0c703ec92fd155645)
- [「Docker」を全く知らない人のために「Docker」の魅力を伝えるための「Docker」入門](https://qiita.com/bremen/items/4604f530fe25786240db)

さて、入門以前に入っていきましょう。
動かすまでの手順というイメージです。

# dockerをインストールする

[docker公式サイト](https://www.docker.com/)からインストールしてきましょう。

右上のGet Startedから、[Get Started with Docker](https://www.docker.com/get-started)のページに行って、 `Download for Mac` をクリックします。

DockerHubの[Docker Desktop for Mac](https://hub.docker.com/editions/community/docker-ce-desktop-mac)というページに飛ばされるので、サインアップなどを済ませて `Get Docker` しましょう。 

`Docker.dmg`

というファイルがダウンロードできたら、展開してインストールを進めます。
しばらくGUIの指示に従ってインストールを進めると、処理が完了しdockerが使えるようになります。

terminal上で

```terminal
docker -v
```

として、

`Docker version 18.09.2, build 6247962`

のような表示が出れば成功です。(インストールだけは昔からしているので、↑はちょっと古いかも)

# dockerを動かす

dockerを動かすまでには、いくつかのステップがあります。
最低限以下のことがわかっていれば、dockerを試すところまではいけると思います。

- docker imageを取得する
- docker imageからコンテナを起動する

それでは早速やっていきましょう。

## docker imageを取得する

以下のコマンドを打てば取得できます。

```terminal
docker pull golang:1.12.3-alpine3.9
```

このコマンドについてもうちょっとだけ説明していきます。
まず、

```terminal
docker pull image名:タグ名
```

とすることで、 docker registry と呼ばれる docker image が保管されている場所から docker image を取得してくることができます。

取得してきた docker image の一覧は

```terminal
docker images
```

を実行することで確認できます。

### docker imageってなに？

とはいえ docker image って何よ、という話なのですが、端的にいうと**「サーバーの設計書」**です。
docker は docker image があれば、 docker container と呼ばれるサーバーをセットアップし、起動することができるのです。

今回でいうと、 `golang` というのが docker image の名称になります。

### docker imageのタグについて

では、例にあるコマンドの `1.12.3-alpine3.9` ってなんなのでしょうか。
これは**タグ**と呼ばれる情報で、取得するイメージのバージョンや環境などを選択できます。

`1.12.3` の部分はgoのバージョンを表しています。

そのあとはモノによるのですが、今回のように追加情報が記載されていることがあります。

#### stretch と alpine について

`golang` イメージには主だったもので `stretch` 系と `alpine` 系のタグが存在します。

タグごとに docker image を定義している Dockerfile の記載が異なるので、動作が異なります。
stretch系はdebian系のimageで、 g++, gcc, libc あたりの機能をはじめにインストールしているみたいです。純粋にgolangを動かしたい方に向いている印象です。

alpine系はlinux系のimageで、あらかじめ bash, openssl などをインストールしているため通信系の機能などを含むアプリケーションの場合は取り回しがしやすいです。簡易的にWebAPIを作りたいようなときはこちらが便利そうですね。

色々と使いやすいのもあり、今回は `1.12.3-alpine3.9` を利用します。

なお、Dockerfileのレベルで確認したい方は以下のリンクが参考になります。
docker、最初はとっつきづらいなと思っていたんですが、慣れてくるとあらゆる情報がDockerHubなどを通じて公開されているのでこういうところがとても追いかけやすいなあ、と感じました。

- [1.12.3-stretch のDockerfile](https://github.com/docker-library/golang/blob/ec8ae35b2c40c81dcbce3d124e097df8c937d5cb/1.12/stretch/Dockerfile)
- [1.12.3-alpine3.9 のDockerfile](https://github.com/docker-library/golang/blob/ec8ae35b2c40c81dcbce3d124e097df8c937d5cb/1.12/alpine3.9/Dockerfile)

## docker imageからコンテナを起動する

```terminal
docker run image名:タグ名
```

で起動できます[^4]。

このコマンドで起動され立ち上がったサーバーのことを、 **dockerコンテナ**と呼びます。

起動時の動作に指定がない docker image の場合、上記のコマンドは成功しますがその後何も起きません。

コンテナ起動時には、パターンに応じて以下のオプションを理解しておくと良いと思います。

### dockerコンテナのなかでbash等のコマンドを実行したい

bashなどのコマンドを実行したい場合、 -it オプションを用います。[^5]

```terminal
docker run -it image名:タグ名
```

### dockerコンテナをホストマシンのディレクトリでマウントして、webサーバーとして起動したい

開発用のwebサーバーとしてdockerコンテナを起動したい場合、

- 手元のコードを反映してほしい
- ローカルマシンから localhost:8080 とかでアクセスできるサーバーとして立ち上がってほしい

というニーズがあると思います。
その実現にあたっては、ざっくり以下のことを把握しておくと良いと思います。

- -p オプションで port forwarding
- -v オプションで マウント元とマウント先の指定
- -e オプションで 環境変数の指定
- -d オプションで バックグラウンド実行
- -it オプション + image:tag の後にコマンドを記載することで、webサーバー起動コマンドの流し込み

上記を踏まえた実行時のterminalのイメージは以下のような感じ。
`/path/to/host` や `/path/to/docker` は仮のパスなので適宜読み替えてくださいませ。

```terminal
docker run -it \                                     # コンテナ内部に入ってbashの操作を行う。最終行でコマンド指定
           -p 8080:8080 \                            # ホストマシンのポートとdockerコンテナのポートを接続する。
           -v /path/to/host/go:/path/to/docker/go \  # ホストマシンのディレクトリ情報でdockerコンテナの中身をマウントする。フルパスじゃないと動かないかも
           -e ENV_VARIABLE=env_value \               # 環境変数の指定
           -e ENV_VARIABLE2=env_value2 \             # 環境変数を複数指定する場合は繰り返し用いる
           -d \                                      # バックグラウンド実行
           image:tag \                               # 使用するdocker imageの指定。確か公式リポジトリにあるやつならdocker runするときに取ってきてくれる
           go run main.go /path/to/docker/go/main.go # 実行するコマンド。 -it コマンドの初期値として与えることができる
```

なお、 `docker run` の際にはタグ名を省略することもできますが、同じ名称のimageを複数pullしてきている場合はタグまで書いた方がわかりやすいです。

ここで起動されたサーバーのことを**docker container**と呼びます。

なお、コンテナを動かすにあたっては、こちらの記事がより参考になります。コマンドの実例集は本当にありがたい。。

- [Dockerコマンドメモ](https://qiita.com/curseoff/items/a9e64ad01d673abb6866)

## 起動したコンテナを使って開発や動作確認をする

動く docker run コマンドの例を書いてみます。
とりあえず動かしたい！ という方は真似してみてください。

事前に、上記のdocker imageを取得しておいてください。

一旦仮定として、以下のパスにいることにしましょう。
`/YOUR_USER_NAME` や `testproject` は適宜読み替えをお願いします。
また、 $GOPATH は `~/go` という仮定で話を進めます。

```terminal
pwd #=> /Users/YOUR_USER_NAME/go/src/github.com/testproject/
```

ここでいつものサンプルを作成します。

```/Users/YOUR_USER_NAME/go/src/github.com/testproject/main.go
package main

import "github.com/gin-gonic/gin"

func main() {
  r := gin.Default()
  r.GET("/ping", func(c *gin.Context) {
    c.JSON(200, gin.H{
      "message": "pong",
    })
  })
  r.Run() // listen and serve on 0.0.0.0:8080
}
```

この状態で、以下のようなコマンドを実行します。

```
docker run -it \
           -p 8080:8080 \
           -v /Users/YOUR_USER_NAME/go/src/github.com/testproject:/go/src/github.com/testproject \
           golang:1.12.3-alpine3.9 \
           go run /go/src/github.com/testproject/main.go
```

これを実行すると、 docker image を元にした docker container が起動し、ローカルサーバーが立ち上がります。

localhost:8080/ping

にアクセスして、 `{"message": "pong"}` のレスポンスが返ってくれば成功です。

### その他のコマンドについて

以下を覚えておくと入門以前のレベルとしては十分だと思います。

```
docker images              # 手元にある docker image が確認できる
docker rmi [IMAGE ID]      # 指定したimageが削除できる。IMAGE IDは docker images で確認できる
docker ps                  # 起動中のコンテナが確認できる
docker kill [CONTAINER ID] # 指定したcontainerを停止できる。CONTAINER IDは docker ps で確認できる
```

## Dockerfile使ったりdocker buildしたりしたい。そしてコンテナをデプロイしたい

そんな感じで、dockerのなんとなくの概要とかをまとめつつ、docker runでサーバーが起動するところまでを駆け足でまとめてみました。
Dockerfileってなに？
手元で動くだけじゃ困るんだけど？
みたいな悩みが本番デプロイやdocker imageの配布などを行いたい人には発生すると思うのですが、これは次回以降に続きます。続け！

[^1]: docker-composeとは、複数のdockerコンテナ(記事内で言及予定)を構成管理できるツールです。よくセットで出てくるけどdockerに対するメタツールなので必須ではないものです。例えばWebアプリケーションサーバーとしてのコンテナと、MySQLサーバーとしてのコンテナを同時に立ち上げ、接続設定まで済ませるといったことが可能です。この記事ではできる限りシンプルにdockerの動かし方を学びたい(しかもこれ書いてる人もdocker-composeよくわかってない)ため、言及していません
[^2]: 完全に余談ですが、シミュレートは内部の動作を再現すること、エミュレートは目に見える動作を再現することを指すらしく、位置付けがけっこう異なるようです。高度に完成されたシミュレーションとエミュレーションは同じ結果をもたらす、とも言われています。
[^3]: IE8を再現するためのVirtualBoxイメージとかね・・・
[^4]: Vagrantでの `vagrant up` にあたります
[^5]: Vagrantでの `vagrant up → vagrant ssh` にあたります